Merge remote-tracking branch 'iaacornu/master' into fix-merge

pull/758/head
HallowedDust5 3 years ago
commit 39160f49bf
No known key found for this signature in database
GPG Key ID: AAAAF940F1C8C004

1
.gitignore vendored

@ -233,6 +233,7 @@ fabric.properties
assets/
out
venv
.DS_Store
.setup-done-before
results/*

@ -43,13 +43,16 @@ The only original thing being done is the editing and gathering of all materials
2b **Manual Install**: 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 `pip install -r requirements.txt`
4. Enjoy 😎
4. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command)
5. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command)
5. Run `python main.py` (unless you chose automatic install, then the installer will automatically run main.py)
6. Run `python main.py` (unless you chose automatic install, then the installer will automatically run main.py)
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.
6. Enjoy 😎
7. Enjoy 😎
There's also now also a CLI! Run `python3 cli.py [OPTIONS (arguments)]` to do the task, do `python3 cli.py -h` for help.
(Note if you got an error installing or running the bot try first rerunning the command with a three after the name e.g. python3 or pip3)

@ -1,2 +1 @@
#!/bin/sh
docker build -t rvmt .

@ -0,0 +1,83 @@
import argparse
from main import main
from setup_program import setup
from utils.console import print_substep
def program_options():
description = """\
Create Reddit Videos with one command.
"""
parser = argparse.ArgumentParser(
prog="RedditVideoMakerBot", # can be renamed, just a base
usage="RedditVideoMakerBot [OPTIONS]",
description=description
)
parser.add_argument(
"-c", "--create",
help="Create a video (uses the defaults).",
action="store_true"
)
parser.add_argument( # only accepts the name of subreddit, not links.
"-s", "--subreddit",
help="Specify a subreddit.",
action="store"
)
parser.add_argument(
"-b", "--background",
help="Specify a video background for video (accepts link and file).",
action="store"
)
parser.add_argument(
"-f", "--filename",
help="Specify a filename for the video.",
action="store"
)
parser.add_argument(
"-t", "--thread",
help="Use the given thread link instead of random.",
action="store"
)
parser.add_argument(
"-n", "--number",
help="Specify number of comments to include in the video.",
action="store"
)
parser.add_argument(
"--setup", "--setup",
help="(Re)setup the program.",
action="store_true"
)
args = parser.parse_args()
try:
if args.create:
while True:
create = main(
args.subreddit,
args.background,
args.filename,
args.thread,
args.number,
)
if not create:
try_again = input("Something went wrong! Try again? [y/N] > ").strip()
if try_again in ["y", "Y"]:
continue
break
elif args.setup:
setup()
else:
print_substep("Error occured!", style_="bold red")
raise SystemExit()
except KeyboardInterrupt:
print_substep("\nOperation Aborted!", style_="bold red")
if __name__ == "__main__":
program_options()

@ -31,7 +31,13 @@ print_markdown(
)
def main():
def main(
subreddit_=None,
background=None,
filename=None,
thread_link_=None,
number_of_comments=None
):
if check_env() is not True:
exit()
load_dotenv()
@ -39,12 +45,16 @@ def main():
reddit_object = get_subreddit_threads()
reddit_object = get_subreddit_threads(
subreddit_,
thread_link_,
number_of_comments
)
length, number_of_comments = save_text_to_mp3(reddit_object)
download_screenshots_of_reddit_posts(reddit_object, number_of_comments)
download_background()
download_background(background)
chop_background_video(length)
make_final_video(number_of_comments, length)
make_final_video(number_of_comments, length,filename)
def run_many(times):

@ -1,3 +1,5 @@
import random
import os
import re
from os import getenv, environ
@ -22,77 +24,144 @@ def try_env(param, backup):
return backup
def get_subreddit_threads():
from prawcore.exceptions import (
OAuthException,
ResponseException,
RequestException,
BadRequest
)
from dotenv import load_dotenv
from utils.console import print_step, print_substep
def get_subreddit_threads(subreddit_, thread_link_, number_of_comments):
"""
Returns a list of threads from the AskReddit subreddit.
Takes subreddit_ as parameter which defaults to None, but in this
case since it is None, it would raise ValueError, thus defaulting
to AskReddit.
Returns a list of threads from the provided subreddit.
"""
global submission
print_substep("Logging into Reddit.")
load_dotenv()
content = {}
if str(getenv("REDDIT_2FA")).casefold() == "yes":
if os.getenv("REDDIT_2FA", default="no").casefold() == "yes":
print(
"\nEnter your two-factor authentication code from your authenticator app.\n"
"\nEnter your two-factor authentication code from your authenticator app.\n", end=" "
)
code = input("> ")
print()
pw = getenv("REDDIT_PASSWORD")
pw = os.getenv("REDDIT_PASSWORD")
passkey = f"{pw}:{code}"
else:
passkey = getenv("REDDIT_PASSWORD")
passkey = os.getenv("REDDIT_PASSWORD")
content = {}
try:
reddit = praw.Reddit(
client_id=getenv("REDDIT_CLIENT_ID"),
client_secret=getenv("REDDIT_CLIENT_SECRET"),
user_agent="Accessing Reddit threads",
username=getenv("REDDIT_USERNAME"),
passkey=passkey,
check_for_async=False,
client_id=os.getenv("REDDIT_CLIENT_ID").strip(),
client_secret=os.getenv("REDDIT_CLIENT_SECRET").strip(),
user_agent="Accessing AskReddit threads",
username=os.getenv("REDDIT_USERNAME").strip(),
password=passkey.strip(),
)
"""
Ask user for subreddit input
"""
print_step("Getting subreddit threads...")
if not getenv(
"SUBREDDIT"
): # note to user. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython")
except (
OAuthException,
ResponseException,
RequestException,
BadRequest
):
print_substep(
"[bold red]There is something wrong with the .env file, kindly check:[/bold red]\n"
+ "1. ClientID\n"
+ "2. ClientSecret\n"
+ "3. If these variables are fine, kindly check other variables.\n"
+ "4. Check if the type of Reddit app created is script (personal use script)."
)
# If the user specifies that he doesnt want a random thread, or if
# he doesn't insert the "RANDOM_THREAD" variable at all, ask the thread link
while True:
if thread_link_ is not None:
thread_link = thread_link_
print_step("Getting the inserted thread...")
submission = reddit.submission(url=thread_link)
else:
try:
if subreddit_ is None:
raise ValueError
subreddit = reddit.subreddit(
re.sub(
r"r\/", "", input("What subreddit would you like to pull from? ")
)
# removes the r/ from the input
re.sub(r"r\/", "", subreddit_.strip())
)
except ValueError:
if os.getenv("SUBREDDIT"):
subreddit = reddit.subreddit(
re.sub(r"r\/", "", os.getenv("SUBREDDIT").strip())
)
else:
subreddit = reddit.subreddit("askreddit")
print_substep("Subreddit not defined. Using AskReddit.")
else:
threads = subreddit.hot(limit=25)
submission = list(threads)[random.randrange(0, 25)]
try:
with open("created_videos", "r", encoding="utf-8") as reference:
videos = list(reference.readlines())
if submission.title in videos:
print_substep(
f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config"
"[bold]There is already a video for thread: [cyan]"
+ f"{submission.title}[/cyan]. Finding another one.[/bold]"
)
continue
except FileNotFoundError:
break
if len(submission.comments) == 0:
print_substep(
"The thread do not contain any comments. Searching for new one.", style_="bold"
)
subreddit = reddit.subreddit(
getenv("SUBREDDIT")
) # Allows you to specify in .env. Done for automation purposes.
if getenv("POST_ID"):
submission = reddit.submission(id=getenv("POST_ID"))
else:
threads = subreddit.hot(limit=25)
submission = get_subreddit_undone(threads, subreddit)
submission = check_done(submission) # double checking
if submission is None:
return get_subreddit_threads() # submission already done. rerun
upvotes = submission.score
ratio = submission.upvote_ratio * 100
num_comments = submission.num_comments
print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green")
print_substep(f"Thread has {upvotes} upvotes", style="bold blue")
print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue")
print_substep(f"Thread has {num_comments} comments", style="bold blue")
environ["VIDEO_TITLE"] = str(
textify(submission.title)
) # todo use global instend of env vars
environ["VIDEO_ID"] = str(textify(submission.id))
print_substep(
f"[bold]Video will be: [cyan]{submission.title}[/cyan] :thumbsup:\n"
+ f"[blue] Thread has {upvotes} and upvote ratio of {ratio}%\n"
+ f"And has a {num_comments}[/blue].\n"
)
try:
content["thread_url"] = submission.url
content["thread_title"] = submission.title
content["thread_post"] = submission.selftext
content["comments"] = []
comment_count = 0
for top_level_comment in submission.comments:
if number_of_comments is not None:
if comment_count == number_of_comments:
break
if not top_level_comment.stickied:
content["comments"].append(
{
"comment_body": top_level_comment.body,
"comment_url": top_level_comment.permalink,
"comment_id": top_level_comment.id,
}
)
count += 1
except AttributeError:
pass
print_substep("AskReddit threads retrieved successfully.", style_="bold green")
content["thread_url"] = f"https://reddit.com{submission.permalink}"
content["thread_title"] = submission.title

@ -1,2 +1 @@
#!/bin/sh
docker run -v $(pwd)/out/:/app/assets -v $(pwd)/.env:/app/.env -it rvmt

@ -0,0 +1,69 @@
import os
from os.path import exists
from utils.console import print_markdown, print_substep
def setup():
if exists(".setup-done-before"):
print_substep(
"Setup was already done before! Please make sure you have "
+ "to run this script again.", style_="bold red"
)
# to run the setup script again, but in better and more convenient manner.
if str(input("\033[1mRun the script again? [y/N] > \033[0m")).strip() not in ["y", "Y"]:
print_substep("Permission denied!", style_="bold red")
raise SystemExit()
print_markdown(
"### You're in the setup wizard. Ensure you're supposed to be here, "
+ "then type yes to continue. If you're not sure, type no to quit."
)
# This Input is used to ensure the user is sure they want to continue.
# Again, let them know they are about to erase all other setup data.
print_substep(
"Ensure you have the following ready to enter:\n"
+ "[bold green]Reddit Client ID\n"
+ "Reddit Client Secret\n"
+ "Reddit Username\n"
+ "Reddit Password\n"
+ "Reddit 2FA (yes or no)\n"
+ "Opacity (range of 0-1, decimals are accepted.)\n"
+ "Subreddit (without r/ or /r/)\n"
+ "Theme (light or dark)[/bold green]"
+ "[bold]If you don't have these, please follow the instructions in the README.md "
+ "set them up.\nIf you do have these, type y or Y to continue. If you don't, "
+ "go ahead and grab those quickly and come back.[/bold]"
)
# Begin the setup process.
cliID = input("Client ID > ")
cliSec = input("Client Secret > ")
user = input("Username > ")
passw = input("Password > ")
twofactor = input("2FA Enabled? (yes/no) > ")
opacity = input("Opacity? (range of 0-1) > ")
subreddit = input("Subreddit (without r/) > ")
theme = input("Theme? (light or dark) > ")
if exists(".env"):
os.remove(".env")
with open(".env", "a", encoding="utf-8") as f:
f.write(
f'REDDIT_CLIENT_ID="{cliID}"\n'
+ f'REDDIT_CLIENT_SECRET="{cliSec}"\n'
+ f'REDDIT_USERNAME="{user}"\n'
+ f'REDDIT_PASSWORD="{passw}"\n'
+ f'REDDIT_2FA="{twofactor}"\n'
+ f'THEME="{theme}"\n'
+ f'SUBREDDIT="{subreddit}"\n'
+ f'OPACITY="{opacity}"\n'
)
with open(".setup-done-before", "a", encoding="utf-8") as f:
f.write("This file will stop the setup assistant from running again.")
print_substep("[bold green]Setup Complete![/bold green]")

@ -1,4 +1,3 @@
#!/usr/bin/env python3
from rich.console import Console
from rich.markdown import Markdown
from rich.padding import Padding
@ -7,6 +6,7 @@ from rich.text import Text
from rich.columns import Columns
import re
console = Console()
@ -24,10 +24,11 @@ def print_step(text):
console.print(panel)
def print_substep(text, style=""):
def print_substep(text, style_=None):
"""Prints a rich info message without the panelling."""
console.print(text, style=style)
if style_ is not None:
console.print(text, style=style_)
console.print(text)
def print_table(items):
"""Prints items in a table."""
@ -74,3 +75,5 @@ def handle_input(
console.print("[red]" + err_message)
return user_input

@ -2,13 +2,18 @@ import random
from os import listdir, environ
from pathlib import Path
from random import randrange
import shutil
from pytube import YouTube
import re
import os
from random import randrange
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:int, length_of_clip:int)->tuple[int,int]:
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 beckground of the video.
Args:
@ -22,39 +27,58 @@ def get_start_and_end_times(video_length:int, length_of_clip:int)->tuple[int,int
return random_time, random_time + video_length
def download_background():
"""Downloads the backgrounds/s video from YouTube."""
def download_background(background: str):
"""Downloads given link, or if it's a path, copies that file over to assets/backgrounds/
Args:
background (str): Youtube link or file path
"""
yt_id_pattern = re.compile(
pattern=r'(?:https?:\/\/)?(?:[0-9A-Z-]+\.)?(?:youtube|youtu|youtube-nocookie)\.(?:com|be)\/(?:watch\?v=|watch\?.+&v=|embed\/|v\/|.+\?v=)?([^&=\n%\?]{11})')
Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True)
background_options = [ # uri , filename , credit
("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"),
# (
background_options = {
"https://www.youtube.com/watch?v=n_Dv4JMiwK8",
# "https://www.youtube.com/watch?v=2X9QGY__0II",
# "rocket_league.mp4",
# "Orbital Gameplay",
# ),
]
}
if Path(background).is_file(): # If background is a file
shutil.copyfile(background, 'assets/backgrounds')
# If background has a youtube video id, so if it's a youtube link
elif re.findall(yt_id_pattern, background, re.IGNORECASE):
background_options.add(background)
# note: make sure the file name doesn't include an - in it
if not len(listdir("./assets/backgrounds")) >= len(
if len(listdir("./assets/backgrounds")) < len(
background_options
): # if there are any background videos not installed
print_step(
"We need to download the backgrounds videos. they are fairly large but it's only done once. 😎"
)
print_substep("Downloading the backgrounds videos... please be patient 🙏 ")
for uri, filename, credit in background_options:
if Path(f"assets/backgrounds/{credit}-{filename}").is_file():
continue # adds check to see if file exists before downloading
print_substep(f"Downloading {filename} from {uri}")
YouTube(uri).streams.filter(res="1080p").first().download(
"assets/backgrounds", filename=f"{credit}-{filename}"
print_substep(
"Downloading the backgrounds videos... please be patient 🙏 ")
for link in background_options:
filename = re.match(yt_id_pattern, link, re.IGNORECASE).string
if not Path(f"assets/backgrounds/{filename}").is_file():
print_substep(f"Downloading {filename} from {link}")
YouTube(link).streams.filter(res="1080p").first().download(
"assets/backgrounds", filename=f"{filename}"
)
print_substep(
"Background videos downloaded successfully! 🎉", style="bold green"
)
os.remove("assets/mp4/background.mp4")
else:
raise Exception("You didn't input a proper link into -b")
def chop_background_video(video_length:int):
def chop_background_video(video_length: int):
"""Generates the background footage to be used in the video and writes it to assets/temp/background.mp4
Args:
@ -66,7 +90,8 @@ def chop_background_video(video_length:int):
background = VideoFileClip(f"assets/backgrounds/{choice}")
start_time, end_time = get_start_and_end_times(video_length, background.duration)
start_time, end_time = get_start_and_end_times(
video_length, background.duration)
ffmpeg_extract_subclip(
f"assets/backgrounds/{choice}",
start_time,

@ -4,6 +4,9 @@ import os
import time
from os.path import exists
import os
import re
from moviepy.editor import (
VideoFileClip,
AudioFileClip,
@ -25,7 +28,7 @@ console = Console()
W, H = 1080, 1920
def make_final_video(number_of_clips:int, length:int):
def make_final_video(number_of_clips:int, length:int,final_vid_path:str):
"""Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp
Args:
@ -39,18 +42,40 @@ def make_final_video(number_of_clips:int, length:int):
background_clip = (
VideoFileClip("assets/temp/background.mp4")
.without_audio()
.resize(height=H)
.resize(height=1920)
.crop(x1=1166.6, y1=0, x2=2246.6, y2=1920)
)
try:
opacity = float(os.getenv("OPACITY"))
except (
ValueError,
FloatingPointError,
TypeError
):
print_substep(
"Please ensure that OPACITY is between 0 and 1 in .env file", style_="bold red"
)
# Gather all audio clips
audio_clips = []
for i in range(0, number_of_clips):
audio_clips.append(AudioFileClip(f"assets/temp/mp3/{i}.mp3"))
audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3"))
audio_clips.append(AudioFileClip(f"assets/mp3/{i}.mp3"))
audio_clips.insert(0, AudioFileClip("assets/mp3/title.mp3"))
try:
audio_clips.insert(1, AudioFileClip("assets/mp3/posttext.mp3"))
except (
OSError,
FileNotFoundError,
):
print_substep("An error occured! Aborting.", style_="bold red")
raise SystemExit()
else:
audio_concat = concatenate_audioclips(audio_clips)
audio_composite = CompositeAudioClip([audio_concat])
# Get sum of all clip lengths
total_length = sum([clip.duration for clip in audio_clips])
# round total_length to an integer
@ -116,11 +141,16 @@ def make_final_video(number_of_clips:int, length:int):
image_concat.audio = audio_composite
final = CompositeVideoClip([background_clip, image_concat])
if final_vid_path is None:
final_vid_path = re.sub(
"[?\"%*:|<>]/", "", (f"assets/{subreddit.submission.title}.mp4")
)
final.write_videofile(final_vid_path, fps=30, audio_codec="aac", audio_bitrate="192k")
filename = f"{get_video_title()}.mp4"
save_data(filename)
save_data(final_vid_path)
if not exists("./results"):
print_substep("the results folder didn't exist so I made it")
@ -134,7 +164,7 @@ def make_final_video(number_of_clips:int, length:int):
verbose=False,
)
ffmpeg_tools.ffmpeg_extract_subclip(
"assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}"
"assets/temp/temp.mp4", 0, length, targetname=f"results/{final_vid_path}"
)
# os.remove("assets/temp/temp.mp4")

@ -9,11 +9,16 @@ from rich.progress import track
from utils.console import print_step, print_substep
import json
from pathlib import Path
from playwright.sync_api import sync_playwright, ViewportSize
from rich.progress import track
from rich.console import Console
import translators as ts
console = Console()
from utils.console import print_step, print_substep
storymode = False
@ -31,12 +36,12 @@ def download_screenshots_of_reddit_posts(reddit_object:dict[str], screenshot_num
# ! Make sure the reddit screenshots folder exists
Path("assets/temp/png").mkdir(parents=True, exist_ok=True)
with console.status("[bold]Launching Headless Browser ...", spinner="simpleDots"):
with sync_playwright() as p:
print_substep("Launching Headless Browser...")
browser = p.chromium.launch()
context = browser.new_context()
if getenv("THEME").upper() == "DARK":
cookie_file = open("./video_creation/data/cookie-dark-mode.json")
else:

@ -1,4 +1,6 @@
#!/usr/bin/env python
from utils.console import print_step, print_substep
import os

Loading…
Cancel
Save