diff --git a/main.py b/main.py old mode 100644 new mode 100755 index 04e1143..0b35c06 --- a/main.py +++ b/main.py @@ -24,11 +24,6 @@ def main(): def get_obj(): reddit_obj = get_subreddit_threads() - for comment in (reddit_obj["comments"]): - if len(comment) > 250: - print(comment) - reddit_obj["comments"].remove(comment) - print(reddit_obj["comments"]) return reddit_obj reddit_object = get_obj() @@ -36,7 +31,7 @@ def main(): download_screenshots_of_reddit_posts(reddit_object, number_of_comments) download_background() chop_background_video(length) - final_video = make_final_video(number_of_comments) + final_video = make_final_video(number_of_comments, length) def run_many(times): diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 5fc79da..21b2123 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -1,24 +1,26 @@ -import random -from os import getenv, environ +import re +from utils.console import print_step, print_substep import praw +import random +from dotenv import load_dotenv +from os import getenv, environ -from utils.console import print_step, print_substep from utils.videos import check_done - TEXT_WHITELIST = set('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890') - def textify(text): return ''.join(filter(TEXT_WHITELIST.__contains__, text)) + def get_subreddit_threads(): """ Returns a list of threads from the AskReddit subreddit. """ print_step("Getting subreddit threads...") + content = {} if getenv("REDDIT_2FA").casefold() == "yes": print("\nEnter your two-factor authentication code from your authenticator app.\n") @@ -29,29 +31,32 @@ def get_subreddit_threads(): else: passkey = getenv("REDDIT_PASSWORD") reddit = praw.Reddit(client_id=getenv("REDDIT_CLIENT_ID"), client_secret=getenv("REDDIT_CLIENT_SECRET"), - user_agent="Accessing AskReddit threads", username=getenv("REDDIT_USERNAME"), + user_agent="Accessing Reddit threads", username=getenv("REDDIT_USERNAME"), passkey=passkey, ) """ Ask user for subreddit input """ + if not getenv("SUBREDDIT"): + subreddit = reddit.subreddit( + input("What subreddit would you like to pull from? ")) # if the env isnt set, ask user + else: + print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") + 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: - if getenv("SUBREDDIT"): # Allows you to specify in .env. Done for automation purposes. - subreddit = reddit.subreddit(getenv("SUBREDDIT")) - else: - # Prompt the user to enter a subreddit - try: - subreddit = reddit.subreddit(input("What subreddit would you like to pull from? ")) - except ValueError: - subreddit = reddit.subreddit("askreddit") - print_substep("Subreddit not defined. Using AskReddit.") threads = subreddit.hot(limit=25) submission = list(threads)[random.randrange(0, 25)] submission = check_done(submission) + if submission == None: + return get_subreddit_threads() # todo check print_substep(f"Video will be: {submission.title} :thumbsup:") + print_substep( + f'subreddit thread is: {submission.title}\n(if you dont like this, you can change it by exiting and rerunning the program)') + environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars environ["VIDEO_ID"] = str(textify(submission.id)) try: @@ -61,11 +66,11 @@ def get_subreddit_threads(): content["comments"] = [] for top_level_comment in submission.comments: - content["comments"].append( - {"comment_body": top_level_comment.body, "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, }) - + if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): + content["comments"].append( + {"comment_body": top_level_comment.body, "comment_url": top_level_comment.permalink, + "comment_id": top_level_comment.id, }) except AttributeError as e: pass - print_substep("Received subreddit thread Successfully.", style="bold green") + print_substep("Received subreddit threads Successfully.", style="bold green") return content diff --git a/utils/videos.py b/utils/videos.py old mode 100644 new mode 100755 index c163076..d22a110 --- a/utils/videos.py +++ b/utils/videos.py @@ -16,6 +16,5 @@ def check_done(redditobj): # don't set this to be run anyplace that isn't subre 'You already have done this video but since it was declared specifically in the .env file the program will continue') return redditobj print_step('Getting new post as the current one has already been done') - from reddit.subreddit import get_subreddit_threads - return get_subreddit_threads() # recursive func + return None return redditobj diff --git a/video_creation/TTSwrapper.py b/video_creation/TTSwrapper.py index 4498184..6fa9d97 100644 --- a/video_creation/TTSwrapper.py +++ b/video_creation/TTSwrapper.py @@ -1,6 +1,6 @@ -import base64 -import random -import requests +import requests, base64, random, os +import re +from moviepy.editor import AudioFileClip, concatenate_audioclips, CompositeAudioClip # https://twitter.com/scanlime/status/1512598559769702406 voices = [ # DISNEY VOICES @@ -56,19 +56,30 @@ class TTTTSWrapper: # TikTok Text-to-Speech Wrapper self.URI_BASE = 'https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=' def tts(self, req_text: str = "TikTok Text To Speech", filename: str = 'title.mp3', random_speaker: bool = False): - if len(req_text) > 299: - return ValueError("Text too long must be under 299 characters") - if random_speaker: - req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") + req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") + voice = self.randomvoice() if random_speaker else 'en_us_002' - r = requests.post(f"{self.URI_BASE}{voice}&req_text={req_text}&speaker_map_type=0") - vstr = [r.json()["data"]["v_str"]][0] + chunks = [m.group().strip() for m in re.finditer(r' *((.{0,200})(\.|.$))',req_text)] + + audio_clips = [] + + chunkId = 0 + for chunk in chunks: + r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + vstr = [r.json()["data"]["v_str"]][0] + b64d = base64.b64decode(vstr) - b64d = base64.b64decode(vstr) + with open(f"{filename}-{chunkId}", "wb") as out: + out.write(b64d) - with open(filename, "wb") as out: - out.write(b64d) + audio_clips.append(AudioFileClip(f"{filename}-{chunkId}")) + + chunkId = chunkId+1; + + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) @staticmethod def randomvoice(): @@ -76,3 +87,4 @@ class TTTTSWrapper: # TikTok Text-to-Speech Wrapper if ok_or_good == 1: # 1/10 chance of ok voice return random.choice(good_voices['ok']) return random.choice(good_voices['good']) # 9/10 chance of good voice + diff --git a/video_creation/background.py b/video_creation/background.py index 0a3e1ce..268c592 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -1,11 +1,11 @@ import random -from os import listdir, environ, remove +from os import listdir, environ from pathlib import Path from random import randrange - -from moviepy.editor import VideoFileClip +from pytube import YouTube from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip -from yt_dlp import YoutubeDL +from moviepy.editor import VideoFileClip +from rich.progress import Progress from utils.console import print_step, print_substep @@ -30,31 +30,27 @@ def download_background(): background_options): # if there are any background videos not installed print_step("We need to download the backgnrounds videos. they are fairly large but it's only done once. 😎") print_substep("Downloading the backgrounds videos... please be patient 🙏 ") + with Progress() as progress: - for uri, filename, credit in background_options: - filename = f"{credit}-{filename}" - ydl_opts = {'outtmpl': f'assets/backgrounds/_raw_{filename}', 'merge_output_format': 'mp4', } - with YoutubeDL(ydl_opts) as ydl: - ydl.download(uri) - videoclip = VideoFileClip(f"assets/backgrounds/{filename}") - new_clip = videoclip.without_audio() - new_clip.write_videofile(f"assets/backgrounds/{filename}") - remove(f'assets/backgrounds/_raw_{filename}') + download_task = progress.add_task("[green]Downloading...", total=2) + + for uri, filename, credit in background_options: + print_substep(f"Downloading {filename} from {uri}") + YouTube(uri).streams.filter(res="1080p").first().download("assets/backgrounds", + filename=f"{credit}-{filename}") + progress.update(download_task, advance=1) # todo remove print_substep("Background videos downloaded successfully! 🎉", style="bold green") def chop_background_video(video_length): - print_step("Finding a spot in the background video to chop...") + print_step("Finding a spot in the backgrounds video to chop...✂️") choice = random.choice(listdir('assets/backgrounds')) environ["background_credit"] = choice.split('-')[0] + background = VideoFileClip(f"assets/backgrounds/{choice}") + start_time, end_time = get_start_and_end_times(video_length, background.duration) - print_substep(choice) - ffmpeg_extract_subclip( - f"assets/backgrounds/{choice}", - start_time, - end_time, - targetname="assets/temp/background.mp4", - ) - print_substep("Background video chopped successfully!", style="bold green") + ffmpeg_extract_subclip(f'assets/backgrounds/{choice}', start_time, end_time, + targetname="assets/temp/background.mp4", ) + print_substep("Background video chopped successfully! 🎉", style="bold green") diff --git a/video_creation/data/cookie.json b/video_creation/data/cookie-dark-mode.json similarity index 73% rename from video_creation/data/cookie.json rename to video_creation/data/cookie-dark-mode.json index 2e4e116..1ed51a9 100644 --- a/video_creation/data/cookie.json +++ b/video_creation/data/cookie-dark-mode.json @@ -4,5 +4,11 @@ "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", "domain": ".reddit.com", "path": "/" + }, + { + "name": "eu_cookie", + "value": "{%22opted%22:true%2C%22nonessential%22:false}", + "domain": ".reddit.com", + "path": "/" } ] diff --git a/video_creation/data/cookie-light-mode.json b/video_creation/data/cookie-light-mode.json new file mode 100644 index 0000000..87eeec9 --- /dev/null +++ b/video_creation/data/cookie-light-mode.json @@ -0,0 +1,8 @@ +[ + { + "name": "eu_cookie", + "value": "{%22opted%22:true%2C%22nonessential%22:false}", + "domain": ".reddit.com", + "path": "/" + } +] diff --git a/video_creation/final_video.py b/video_creation/final_video.py old mode 100644 new mode 100755 index d5fd528..157b94b --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -3,8 +3,8 @@ import os import time from os.path import exists -from moviepy.editor import (VideoFileClip, AudioFileClip, ImageClip, concatenate_videoclips, concatenate_audioclips, - CompositeAudioClip, CompositeVideoClip) +from moviepy.editor import VideoFileClip, AudioFileClip, ImageClip, concatenate_videoclips, concatenate_audioclips, CompositeAudioClip, CompositeVideoClip +from moviepy.video import io from utils.cleanup import cleanup from utils.console import print_step, print_substep @@ -12,7 +12,7 @@ from utils.console import print_step, print_substep W, H = 1080, 1920 -def make_final_video(number_of_clips): +def make_final_video(number_of_clips, length): print_step("Creating the final video 🎥") VideoFileClip.reW = lambda clip: clip.resize(width=W) VideoFileClip.reH = lambda clip: clip.resize(width=H) @@ -65,7 +65,11 @@ def make_final_video(number_of_clips): if not exists('./results'): print_substep('the results folder didn\'t exist so I made it') os.mkdir("./results") - final.write_videofile(f"results/{filename}", fps=30, audio_codec="aac", audio_bitrate="192k") + + final.write_videofile("temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") + io.ffmpeg_tools.ffmpeg_extract_subclip("temp.mp4", 0, length, targetname=f"results/{filename}") + os.remove("temp.mp4") + print_step("Removing temporary files 🗑") cleanups = cleanup() print_substep(f"Removed {cleanups} temporary files 🗑") diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index bcc3bb0..6c910b8 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -27,9 +27,11 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): context = browser.new_context() if getenv("THEME").upper() == "DARK": - cookie_file = open('./video_creation/data/cookie.json') - cookies = json.load(cookie_file) - context.add_cookies(cookies) + cookie_file = open('./video_creation/data/cookie-dark-mode.json') + else: + cookie_file = open('./video_creation/data/cookie-light-mode.json') + cookies = json.load(cookie_file) + context.add_cookies(cookies) # Get the thread screenshot page = context.new_page() page.set_viewport_size(ViewportSize(width=1920, height=1080))