Merge branch 'JasonLovesDoggo:master' into master
This commit is contained in:
15 files changed
+208
-158
No files matched your search
@@ -2,8 +2,21 @@ REDDIT_CLIENT_ID=""
|
||||
REDDIT_CLIENT_SECRET=""
|
||||
REDDIT_USERNAME=""
|
||||
REDDIT_PASSWORD=""
|
||||
# Valid options are "yes" and "no" for the variable below
|
||||
REDDIT_2FA=""
|
||||
SUBREDDIT="AskReddit"
|
||||
# True or False
|
||||
ALLOW_NSFW="False"
|
||||
# Example of a PostID is ult7el
|
||||
POST_ID=""
|
||||
#set to either LIGHT or DARK
|
||||
THEME="LIGHT"
|
||||
# set to an int e.g. 1 or 29 and leave blank for once
|
||||
TIMES_TO_RUN=""
|
||||
MAX_COMMENT_LENGTH="500"
|
||||
# Range is 0 -> 1 recommended around 0.8-0.9
|
||||
OPACITY=""
|
||||
|
||||
# see TTSwrapper.py for all valid options
|
||||
VOICE="en_us_001" # e.g. en_us_002
|
||||
|
||||
@@ -14,14 +14,16 @@ Created by Lewis Menelaws & [TMRRW](https://tmrrwinc.ca)
|
||||
|
||||
## Motivation 🤔
|
||||
|
||||
These videos on TikTok, YouTube and Instagram get MILLIONS of views across all platforms and require very little effort. The only original thing being done is the editing and gathering of all materials...
|
||||
These videos on TikTok, YouTube and Instagram get MILLIONS of views across all platforms and require very little effort.
|
||||
The only original thing being done is the editing and gathering of all materials...
|
||||
|
||||
... but what if we can automate that process? 🤔
|
||||
|
||||
## Disclaimers 🚨
|
||||
|
||||
- This is purely for fun purposes.
|
||||
- **At the moment**, this repository won't attempt to upload this content through this bot. It will give you a file that you will then have to upload manually. This is for the sake of avoiding any sort of community guideline issues.
|
||||
- **At the moment**, this repository won't attempt to upload this content through this bot. It will give you a file that
|
||||
you will then have to upload manually. This is for the sake of avoiding any sort of community guideline issues.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -31,18 +33,21 @@ These videos on TikTok, YouTube and Instagram get MILLIONS of views across all p
|
||||
## Installation 👩💻
|
||||
|
||||
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` files.
|
||||
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. install [SoX](https://sourceforge.net/projects/sox/files/sox/)
|
||||
5. Run `python3 main.py`
|
||||
6. Enjoy 😎
|
||||
## Usage
|
||||
* an example of a reddit ID is ult7el
|
||||
5. Run `playwright install` and `playwright install-deps`.
|
||||
6. Run `python3 main.py`
|
||||
7. Enjoy 😎
|
||||
|
||||
## Contributing & Ways to improve 📈
|
||||
|
||||
In its current state, this bot does exactly what it needs to do. However, lots of improvements can be made.
|
||||
|
||||
I have tried to simplify the code so anyone can read it and start contibuting at any skill level. Don't be shy :) contribute!
|
||||
I have tried to simplify the code so anyone can read it and start contributing at any skill level. Don't be shy :)
|
||||
contribute!
|
||||
|
||||
- [ ] Creating better documentation and adding a command line interface.
|
||||
- [x] Allowing users to choose a reddit thread instead of being randomized.
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
from utils.cleanup import cleanup
|
||||
from utils.console import print_markdown
|
||||
import time
|
||||
from reddit.subreddit import get_subreddit_threads
|
||||
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
|
||||
from subprocess import Popen
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from os import getenv, name
|
||||
from reddit.subreddit import get_subreddit_threads
|
||||
from utils.cleanup import cleanup
|
||||
from utils.console import print_markdown, print_step
|
||||
from video_creation.background import download_background, chop_background_video
|
||||
from video_creation.final_video import make_final_video
|
||||
from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts
|
||||
from video_creation.voices import save_text_to_mp3
|
||||
|
||||
banner = '''
|
||||
██████╗ ███████╗██████╗ ██████╗ ██╗████████╗ ██╗ ██╗██╗██████╗ ███████╗ ██████╗ ███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
|
||||
██╔══██╗██╔════╝██╔══██╗██╔══██╗██║╚══██╔══╝ ██║ ██║██║██╔══██╗██╔════╝██╔═══██╗ ████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
|
||||
██████╔╝█████╗ ██║ ██║██║ ██║██║ ██║ ██║ ██║██║██║ ██║█████╗ ██║ ██║ ██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
|
||||
██╔══██╗██╔══╝ ██║ ██║██║ ██║██║ ██║ ╚██╗ ██╔╝██║██║ ██║██╔══╝ ██║ ██║ ██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
|
||||
██║ ██║███████╗██████╔╝██████╔╝██║ ██║ ╚████╔╝ ██║██████╔╝███████╗╚██████╔╝ ██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
|
||||
╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
||||
'''
|
||||
print(banner)
|
||||
load_dotenv()
|
||||
# base code by elebumm
|
||||
print_markdown(
|
||||
"### Thanks for using this tool! 😊 Feel free to contribute to this project on GitHub! (JasonLovesDoggo/RedditVideoMakerBot). 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! (JasonLovesDoggo/RedditVideoMakerBot). If you have any questions, feel free to reach out to me on Twitter @JasonLovesDoggo or submit a GitHub issue.")
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
@@ -29,9 +43,21 @@ def main():
|
||||
final_video = make_final_video(number_of_comments, length)
|
||||
|
||||
|
||||
def run_many(times):
|
||||
for x in range(times):
|
||||
x = x + 1
|
||||
print_step(
|
||||
f'on the {x}{("st" if x == 1 else ("nd" if x == 2 else ("rd" if x == 3 else "th")))} iteration of {times}') # correct 1st 2nd 3rd 4th 5th....
|
||||
main()
|
||||
Popen('cls' if name == 'nt' else 'clear', shell=True).wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
if getenv('TIMES_TO_RUN'):
|
||||
run_many(int(getenv('TIMES_TO_RUN')))
|
||||
else:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print_markdown("## Clearing temp files")
|
||||
cleanup()
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
from utils.console import print_step, print_substep
|
||||
import praw
|
||||
import random
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
|
||||
def get_askreddit_threads():
|
||||
"""
|
||||
Returns a list of threads from the AskReddit subreddit.
|
||||
"""
|
||||
|
||||
print_step("Getting AskReddit threads...")
|
||||
|
||||
content = {}
|
||||
load_dotenv()
|
||||
reddit = praw.Reddit(
|
||||
client_id=os.getenv("REDDIT_CLIENT_ID"),
|
||||
client_secret=os.getenv("REDDIT_CLIENT_SECRET"),
|
||||
user_agent="Accessing AskReddit threads",
|
||||
username=os.getenv("REDDIT_USERNAME"),
|
||||
password=os.getenv("REDDIT_PASSWORD"),
|
||||
)
|
||||
askreddit = reddit.subreddit("askreddit")
|
||||
threads = askreddit.hot(limit=25)
|
||||
submission = list(threads)[random.randrange(0, 25)]
|
||||
try:
|
||||
|
||||
content["thread_url"] = submission.url
|
||||
content["thread_title"] = submission.title
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
except AttributeError as e:
|
||||
pass
|
||||
print_substep("Received AskReddit threads Successfully.", style="bold green")
|
||||
return content
|
||||
Executable → Regular
+32
-22
@@ -1,31 +1,37 @@
|
||||
import re
|
||||
|
||||
from utils.console import print_step, print_substep
|
||||
import praw
|
||||
import random
|
||||
from dotenv import load_dotenv
|
||||
from os import getenv, environ
|
||||
|
||||
import praw
|
||||
|
||||
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 selected subreddit.
|
||||
Returns a list of threads from the AskReddit subreddit.
|
||||
"""
|
||||
|
||||
global submission
|
||||
print_step("Getting subreddit threads...")
|
||||
|
||||
content = {}
|
||||
load_dotenv()
|
||||
if getenv("REDDIT_2FA").casefold() == "yes":
|
||||
print("\nEnter your two-factor authentication code from your authenticator app.\n")
|
||||
code = input("> ")
|
||||
print()
|
||||
pw = getenv("REDDIT_PASSWORD")
|
||||
passkey = f"{pw}:{code}"
|
||||
else:
|
||||
passkey = getenv("REDDIT_PASSWORD")
|
||||
reddit = praw.Reddit(client_id=getenv("REDDIT_CLIENT_ID"), client_secret=getenv("REDDIT_CLIENT_SECRET"),
|
||||
user_agent="Accessing subreddit threads", username=getenv("REDDIT_USERNAME"),
|
||||
password=getenv("REDDIT_PASSWORD"), )
|
||||
user_agent="Accessing Reddit threads", username=getenv("REDDIT_USERNAME"),
|
||||
passkey=passkey, check_for_async=False,)
|
||||
"""
|
||||
Ask user for subreddit input
|
||||
"""
|
||||
@@ -43,13 +49,17 @@ def get_subreddit_threads():
|
||||
threads = subreddit.hot(limit=25)
|
||||
submission = list(threads)[random.randrange(0, 25)]
|
||||
submission = check_done(submission)
|
||||
if submission == None:
|
||||
return get_subreddit_threads()
|
||||
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'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))
|
||||
print_substep(f"Video will be: {submission.title} :thumbsup:", style='bold green')
|
||||
print_substep(f"Thread has " + str(upvotes) + " upvotes", style='bold blue')
|
||||
print_substep(f"Thread has a upvote ratio of " + str(ratio) + "%", style='bold blue')
|
||||
print_substep(f"Thread has " + str(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))
|
||||
try:
|
||||
|
||||
@@ -58,11 +68,11 @@ def get_subreddit_threads():
|
||||
content["comments"] = []
|
||||
|
||||
for top_level_comment in submission.comments:
|
||||
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, })
|
||||
|
||||
if not top_level_comment.stickied:
|
||||
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 threads Successfully.", style="bold green")
|
||||
|
||||
+1
-1
@@ -2,8 +2,8 @@ praw~=7.6.0
|
||||
moviepy~=1.0.3
|
||||
rich~=12.4.4
|
||||
mutagen~=1.45.1
|
||||
pytube~=12.1.0
|
||||
playwright~=1.22.0
|
||||
python-dotenv==0.20.0
|
||||
typed-ast~=1.5.4
|
||||
requests~=2.27.1
|
||||
yt-dlp
|
||||
@@ -20,4 +20,3 @@ def cleanup() -> int:
|
||||
os.remove('./assets/temp/mp3/' + file)
|
||||
return count
|
||||
return 0
|
||||
|
||||
+4
-5
@@ -1,4 +1,3 @@
|
||||
import inspect
|
||||
import json
|
||||
from os import getenv
|
||||
|
||||
@@ -8,14 +7,14 @@ from utils.console import print_step
|
||||
def check_done(redditobj): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack
|
||||
"""params:
|
||||
reddit_object: The Reddit Object you received in askreddit.py"""
|
||||
if getenv('POST_ID'):
|
||||
print_step(
|
||||
'You already have done this video but since it was declared specifically in the .env file the program will continue')
|
||||
return redditobj
|
||||
with open('./video_creation/data/videos.json', 'r') as done_vids_raw:
|
||||
done_videos = json.load(done_vids_raw)
|
||||
for video in done_videos:
|
||||
if video['id'] == str(redditobj):
|
||||
if getenv('POST_ID'):
|
||||
print_step(
|
||||
'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')
|
||||
return None
|
||||
return redditobj
|
||||
@@ -0,0 +1,22 @@
|
||||
import re
|
||||
|
||||
|
||||
def sanitize_text(text):
|
||||
"""
|
||||
Sanitizes the text for tts.
|
||||
What gets removed:
|
||||
- following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+`
|
||||
- any http or https links
|
||||
"""
|
||||
|
||||
# remove any urls from the text
|
||||
regex_urls = r'((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*'
|
||||
|
||||
result = re.sub(regex_urls, " ", text)
|
||||
|
||||
# note: not removing apostrophes
|
||||
regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%“”‘\"%\*/{}\[\]\(\)\\|<>=+]"
|
||||
result = re.sub(regex_expr, " ", result)
|
||||
|
||||
# remove extra whitespace
|
||||
return " ".join(result.split())
|
||||
@@ -1,9 +1,15 @@
|
||||
import requests, base64, random, os
|
||||
import re
|
||||
from moviepy.editor import AudioFileClip, concatenate_audioclips, CompositeAudioClip
|
||||
|
||||
import base64
|
||||
import os
|
||||
import random
|
||||
import requests
|
||||
from moviepy.editor import AudioFileClip, concatenate_audioclips, CompositeAudioClip
|
||||
#from profanity_filter import ProfanityFilter
|
||||
#pf = ProfanityFilter()
|
||||
# Code by @JasonLovesDoggo
|
||||
# https://twitter.com/scanlime/status/1512598559769702406
|
||||
voices = [ # DISNEY VOICES
|
||||
nonhuman = [ # DISNEY VOICES
|
||||
'en_us_ghostface', # Ghost Face
|
||||
'en_us_chewbacca', # Chewbacca
|
||||
'en_us_c3po', # C3PO
|
||||
@@ -12,18 +18,20 @@ voices = [ # DISNEY VOICES
|
||||
'en_us_rocket', # Rocket
|
||||
|
||||
# ENGLISH VOICES
|
||||
'en_au_001', # English AU - Female
|
||||
'en_au_002', # English AU - Male
|
||||
'en_uk_001', # English UK - Male 1
|
||||
'en_uk_003', # English UK - Male 2
|
||||
'en_us_001', # English US - Female (Int. 1)
|
||||
'en_us_002', # English US - Female (Int. 2)
|
||||
'en_us_006', # English US - Male 1
|
||||
'en_us_007', # English US - Male 2
|
||||
'en_us_009', # English US - Male 3
|
||||
'en_us_010', # English US - Male 4
|
||||
]
|
||||
human = ['en_au_001', # English AU - Female
|
||||
'en_au_002', # English AU - Male
|
||||
'en_uk_001', # English UK - Male 1
|
||||
'en_uk_003', # English UK - Male 2
|
||||
'en_us_001', # English US - Female (Int. 1)
|
||||
'en_us_002', # English US - Female (Int. 2)
|
||||
'en_us_006', # English US - Male 1
|
||||
'en_us_007', # English US - Male 2
|
||||
'en_us_009', # English US - Male 3
|
||||
'en_us_010']
|
||||
voices = nonhuman + human
|
||||
|
||||
# EUROPE VOICES
|
||||
noneng = [
|
||||
'fr_001', # French - Male 1
|
||||
'fr_002', # French - Male 2
|
||||
'de_001', # German - Female
|
||||
@@ -47,20 +55,24 @@ voices = [ # DISNEY VOICES
|
||||
'kr_003', # Korean - Female
|
||||
'kr_004', # Korean - Male 2
|
||||
]
|
||||
good_voices = {'good': ['en_us_002', 'en_us_006'],
|
||||
'ok': ['en_au_002', 'en_uk_001']} # less en_us_stormtrooper more less en_us_rocket en_us_ghostface
|
||||
|
||||
|
||||
# good_voices = {'good': ['en_us_002', 'en_us_006'],
|
||||
# 'ok': ['en_au_002', 'en_uk_001']} # less en_us_stormtrooper more less en_us_rocket en_us_ghostface
|
||||
|
||||
|
||||
class TTTTSWrapper: # TikTok Text-to-Speech Wrapper
|
||||
def __init__(self):
|
||||
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):
|
||||
def tts(self, req_text: str = "TikTok Text To Speech", filename: str = 'title.mp3', random_speaker: bool = False, censer=False):
|
||||
req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and")
|
||||
if censer:
|
||||
#req_text = pf.censor(req_text)
|
||||
pass
|
||||
voice = self.randomvoice() if random_speaker else (os.getenv('VOICE') or random.choice(human))
|
||||
|
||||
voice = self.randomvoice() if random_speaker else 'en_us_002'
|
||||
|
||||
chunks = [m.group().strip() for m in re.finditer(r' *((.{0,200})(\.|.$))',req_text)]
|
||||
chunks = [m.group().strip() for m in re.finditer(r' *((.{0,200})(\.|.$))', req_text)]
|
||||
|
||||
audio_clips = []
|
||||
|
||||
@@ -75,7 +87,7 @@ class TTTTSWrapper: # TikTok Text-to-Speech Wrapper
|
||||
|
||||
audio_clips.append(AudioFileClip(f"{filename}-{chunkId}"))
|
||||
|
||||
chunkId = chunkId+1;
|
||||
chunkId = chunkId + 1
|
||||
|
||||
audio_concat = concatenate_audioclips(audio_clips)
|
||||
audio_composite = CompositeAudioClip([audio_concat])
|
||||
@@ -85,6 +97,5 @@ class TTTTSWrapper: # TikTok Text-to-Speech Wrapper
|
||||
def randomvoice():
|
||||
ok_or_good = random.randrange(1, 10)
|
||||
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
|
||||
|
||||
return random.choice(voices)
|
||||
return random.choice(human) # 9/10 chance of good voice
|
||||
Executable → Regular
+5
-14
@@ -5,8 +5,6 @@ from random import randrange
|
||||
from pytube import YouTube
|
||||
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
|
||||
from moviepy.editor import VideoFileClip
|
||||
from rich.progress import Progress
|
||||
|
||||
from utils.console import print_step, print_substep
|
||||
|
||||
|
||||
@@ -18,8 +16,6 @@ def get_start_and_end_times(video_length, length_of_clip):
|
||||
def download_background():
|
||||
"""Downloads the backgrounds/s video from youtube.
|
||||
|
||||
Shoutout to: bbswitzer (https://www.youtube.com/watch?v=n_Dv4JMiwK8)
|
||||
Shoutout to: Orbital Gameplay (https://www.youtube.com/watch?v=2X9QGY__0II)
|
||||
"""
|
||||
Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True)
|
||||
background_options = [ # uri , filename , credit
|
||||
@@ -30,15 +26,10 @@ 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:
|
||||
|
||||
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)
|
||||
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}")
|
||||
|
||||
print_substep("Background videos downloaded successfully! 🎉", style="bold green")
|
||||
|
||||
@@ -52,5 +43,5 @@ def chop_background_video(video_length):
|
||||
|
||||
start_time, end_time = get_start_and_end_times(video_length, background.duration)
|
||||
ffmpeg_extract_subclip(f'assets/backgrounds/{choice}', start_time, end_time,
|
||||
targetname="assets/temp/backgrounds.mp4", )
|
||||
targetname="assets/temp/background.mp4", )
|
||||
print_substep("Background video chopped successfully! 🎉", style="bold green")
|
||||
@@ -0,0 +1,2 @@
|
||||
videos.json
|
||||
#todo add videos on github
|
||||
@@ -3,9 +3,11 @@ import os
|
||||
import time
|
||||
from os.path import exists
|
||||
|
||||
from moviepy.editor import VideoFileClip, AudioFileClip, ImageClip, concatenate_videoclips, concatenate_audioclips, CompositeAudioClip, CompositeVideoClip
|
||||
from moviepy.video import io
|
||||
from moviepy.editor import VideoFileClip, AudioFileClip, ImageClip, concatenate_videoclips, concatenate_audioclips, \
|
||||
CompositeAudioClip, CompositeVideoClip
|
||||
from moviepy.video.io import ffmpeg_tools
|
||||
|
||||
from reddit import subreddit
|
||||
from utils.cleanup import cleanup
|
||||
from utils.console import print_step, print_substep
|
||||
|
||||
@@ -16,10 +18,10 @@ 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)
|
||||
|
||||
opacity = os.getenv('OPACITY')
|
||||
background_clip = (
|
||||
VideoFileClip("assets/temp/backgrounds.mp4").without_audio().resize(height=H).crop(x1=1166.6, y1=0, x2=2246.6,
|
||||
y2=1920))
|
||||
VideoFileClip("assets/temp/background.mp4").without_audio().resize(height=H).crop(x1=1166.6, y1=0, x2=2246.6,
|
||||
y2=1920))
|
||||
# Gather all audio clips
|
||||
audio_clips = []
|
||||
for i in range(0, number_of_clips):
|
||||
@@ -33,9 +35,10 @@ def make_final_video(number_of_clips, length):
|
||||
for i in range(0, number_of_clips):
|
||||
image_clips.append(
|
||||
ImageClip(f"assets/temp/png/comment_{i}.png").set_duration(audio_clips[i + 1].duration).set_position(
|
||||
"center").resize(width=W - 100), )
|
||||
"center").resize(width=W - 100).set_opacity(float(opacity)), )
|
||||
image_clips.insert(0, ImageClip(f"assets/temp/png/title.png").set_duration(audio_clips[0].duration).set_position(
|
||||
"center").resize(width=W - 100), )
|
||||
"center").resize(width=W - 100).set_opacity(float(opacity)),
|
||||
)
|
||||
image_concat = concatenate_videoclips(image_clips).set_position(("center", "center"))
|
||||
image_concat.audio = audio_composite
|
||||
final = CompositeVideoClip([background_clip, image_concat])
|
||||
@@ -52,7 +55,7 @@ def make_final_video(number_of_clips, length):
|
||||
def save_data():
|
||||
with open('./video_creation/data/videos.json', 'r+') as raw_vids:
|
||||
done_vids = json.load(raw_vids)
|
||||
if str(os.getenv("VIDEO_ID")) in [video['id'] for video in done_vids]:
|
||||
if str(subreddit.submission.id) in [video['id'] for video in done_vids]:
|
||||
return # video already done but was specified to continue anyway in the .env file
|
||||
payload = {"id": str(os.getenv("VIDEO_ID")), 'time': str(int(time.time())),
|
||||
"background_credit": str(os.getenv('background_credit')),
|
||||
@@ -66,9 +69,9 @@ def make_final_video(number_of_clips, length):
|
||||
print_substep('the results folder didn\'t exist so I made it')
|
||||
os.mkdir("./results")
|
||||
|
||||
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")
|
||||
final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k")
|
||||
ffmpeg_tools.ffmpeg_extract_subclip("assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}")
|
||||
# os.remove("assets/temp/temp.mp4")
|
||||
|
||||
print_step("Removing temporary files 🗑")
|
||||
cleanups = cleanup()
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import json
|
||||
from os import getenv
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
from pathlib import Path
|
||||
|
||||
from playwright.async_api import async_playwright
|
||||
from playwright.sync_api import sync_playwright, ViewportSize
|
||||
from rich.progress import track
|
||||
|
||||
from utils.console import print_step, print_substep
|
||||
|
||||
|
||||
def download_screenshots_of_reddit_posts(reddit_object, screenshot_num):
|
||||
"""Downloads screenshots of reddit posts as they are seen on the web.
|
||||
|
||||
@@ -33,15 +35,15 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num):
|
||||
context.add_cookies(cookies)
|
||||
# Get the thread screenshot
|
||||
page = context.new_page()
|
||||
page.set_viewport_size(ViewportSize(width=1920, height=1080))
|
||||
page.goto(reddit_object["thread_url"])
|
||||
|
||||
if page.locator('[data-testid="content-gate"]').is_visible():
|
||||
# This means the post is NSFW and requires to click the proceed button.
|
||||
if getenv("ALLOW_NSFW").casefold() == "false":
|
||||
print_substep("NSFW Post Detected. Skipping...")
|
||||
from subprocess import call
|
||||
call(["python", "main.py"])
|
||||
exit(1)
|
||||
from main import main
|
||||
main()
|
||||
|
||||
print_substep("Post is NSFW. You are spicy... :fire:")
|
||||
page.locator('[data-testid="content-gate"] button').click()
|
||||
|
||||
+25
-13
@@ -1,13 +1,16 @@
|
||||
import sox
|
||||
from pathlib import Path
|
||||
|
||||
import sox
|
||||
from mutagen import MutagenError
|
||||
from mutagen.mp3 import MP3, HeaderNotFoundError
|
||||
from utils.console import print_step, print_substep
|
||||
from rich.progress import track
|
||||
|
||||
from utils.console import print_step, print_substep
|
||||
from utils.voice import sanitize_text
|
||||
from video_creation.TTSwrapper import TTTTSWrapper
|
||||
|
||||
VIDEO_LENGTH: int = 40 # secs
|
||||
|
||||
|
||||
def save_text_to_mp3(reddit_obj):
|
||||
"""Saves Text to MP3 files.
|
||||
@@ -22,23 +25,32 @@ def save_text_to_mp3(reddit_obj):
|
||||
Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
ttttsw = TTTTSWrapper() # tiktok text to speech wrapper
|
||||
ttttsw.tts(reddit_obj["thread_title"], filename=f"assets/temp/mp3/title.mp3", random_speaker=True)
|
||||
ttttsw.tts(sanitize_text(reddit_obj["thread_title"]), filename=f"assets/temp/mp3/title.mp3", random_speaker=False)
|
||||
try:
|
||||
length += MP3(f"assets/temp/mp3/title.mp3").info.length
|
||||
except HeaderNotFoundError:
|
||||
length = sox.file_info.duration(f"assets/temp/mp3/title.mp3")
|
||||
|
||||
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_voices starting point
|
||||
if length > 50:
|
||||
except HeaderNotFoundError: # note to self AudioFileClip
|
||||
length += sox.file_info.duration(f"assets/temp/mp3/title.mp3")
|
||||
com = 0
|
||||
for comment in track((reddit_obj["comments"]), "Saving..."):
|
||||
# ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer, but this is just a good_voices starting point
|
||||
if length > VIDEO_LENGTH:
|
||||
break
|
||||
|
||||
ttttsw.tts(comment["comment_body"], filename=f"assets/temp/mp3/{idx}.mp3", random_speaker=False)
|
||||
ttttsw.tts(sanitize_text(comment["comment_body"]), filename=f"assets/temp/mp3/{com}.mp3", random_speaker=False)
|
||||
try:
|
||||
length += MP3(f"assets/temp/mp3/{idx}.mp3").info.length
|
||||
length += MP3(f"assets/temp/mp3/{com}.mp3").info.length
|
||||
com += 1
|
||||
except (HeaderNotFoundError, MutagenError, Exception):
|
||||
length = sox.file_info.duration(f"assets/temp/mp3/{idx}.mp3")
|
||||
try:
|
||||
length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3")
|
||||
com += 1
|
||||
except (OSError, IOError):
|
||||
print('would have removed'
|
||||
f"assets/temp/mp3/{com}.mp3"
|
||||
f"assets/temp/png/comment_{com}.png")
|
||||
# remove(f"assets/temp/mp3/{com}.mp3")
|
||||
# remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing
|
||||
|
||||
print_substep("Saved Text to MP3 files Successfully.", style="bold green")
|
||||
# ! Return the index so we know how many screenshots of comments we need to make.
|
||||
return length, idx
|
||||
return length, com
|
||||
Reference in new issue
Block a user