commit
d5e5eb4367
@ -1,29 +1,39 @@
|
|||||||
|
# This can be found in the email that reddit sent you when you created the app
|
||||||
REDDIT_CLIENT_ID=""
|
REDDIT_CLIENT_ID=""
|
||||||
REDDIT_CLIENT_SECRET=""
|
REDDIT_CLIENT_SECRET=""
|
||||||
|
|
||||||
REDDIT_USERNAME=""
|
REDDIT_USERNAME=""
|
||||||
REDDIT_PASSWORD=""
|
REDDIT_PASSWORD=""
|
||||||
|
|
||||||
# Valid options are "yes" and "no"
|
# If no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no"
|
||||||
REDDIT_2FA=""
|
RANDOM_THREAD=""
|
||||||
|
|
||||||
#If no, it will ask you a thread link to extract the thread, if yes it will randomize it.
|
|
||||||
RANDOM_THREAD="yes"
|
|
||||||
|
|
||||||
# Valid options are "light" and "dark"
|
|
||||||
THEME=""
|
|
||||||
|
|
||||||
# Enter a subreddit, e.g. "AskReddit"
|
|
||||||
SUBREDDIT=""
|
|
||||||
|
|
||||||
# Filters the comments by range of lenght (min and max characters)
|
# Filters the comments by range of lenght (min and max characters)
|
||||||
# Min has to be less or equal to max
|
# Min has to be less or equal to max
|
||||||
# DO NOT INSERT ANY SPACES BETWEEN THE COMMA AND THE VALUES
|
# DO NOT INSERT ANY SPACES BETWEEN THE COMMA AND THE VALUES
|
||||||
COMMENT_LENGTH_RANGE = "min,max"
|
COMMENT_LENGTH_RANGE = "min,max"
|
||||||
|
|
||||||
# Range is 0 -> 1
|
|
||||||
OPACITY="0.9"
|
|
||||||
|
|
||||||
# The absolute path of the folder where you want to save the final video
|
# The absolute path of the folder where you want to save the final video
|
||||||
# If empty or wrong, the path will be 'assets/'
|
# If empty or wrong, the path will be 'results/'
|
||||||
FINAL_VIDEO_PATH=""
|
FINAL_VIDEO_PATH=""
|
||||||
|
# Valid options are "yes" and "no" for the variable below
|
||||||
|
REDDIT_2FA=""
|
||||||
|
SUBREDDIT="AskReddit"
|
||||||
|
# True or False
|
||||||
|
ALLOW_NSFW="False"
|
||||||
|
# Used if you want to use a specific post. example of one is urdtfx
|
||||||
|
POST_ID=""
|
||||||
|
#set to either LIGHT or DARK
|
||||||
|
THEME="LIGHT"
|
||||||
|
# used if you want to run multiple times. set to an int e.g. 4 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="1"
|
||||||
|
|
||||||
|
# see TTSwrapper.py for all valid options
|
||||||
|
VOICE="Matthew" # e.g. en_us_002
|
||||||
|
TTsChoice="polly" # todo add docs
|
||||||
|
|
||||||
|
# IN-PROGRESS - not yet implemented
|
||||||
|
STORYMODE="False"
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "pip" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
@ -0,0 +1,13 @@
|
|||||||
|
from gtts import gTTS
|
||||||
|
|
||||||
|
|
||||||
|
class GTTS:
|
||||||
|
def tts(
|
||||||
|
self,
|
||||||
|
req_text: str = "Google Text To Speech",
|
||||||
|
filename: str = "title.mp3",
|
||||||
|
random_speaker=False,
|
||||||
|
censer=False,
|
||||||
|
):
|
||||||
|
tts = gTTS(text=req_text, lang="en", slow=False)
|
||||||
|
tts.save(f"{filename}")
|
@ -0,0 +1,115 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import sox
|
||||||
|
from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip
|
||||||
|
from moviepy.audio.io.AudioFileClip import AudioFileClip
|
||||||
|
voices = {'neural': [
|
||||||
|
'Ivy',
|
||||||
|
'Joanna',
|
||||||
|
'Kendra',
|
||||||
|
'Kimberly',
|
||||||
|
'Salli',
|
||||||
|
'Joey',
|
||||||
|
'Justin',
|
||||||
|
'Matthew',
|
||||||
|
'Amy',
|
||||||
|
'Emma',
|
||||||
|
'Brian'
|
||||||
|
|
||||||
|
], 'standard': [
|
||||||
|
'Ivy',
|
||||||
|
'Joanna',
|
||||||
|
'Kendra',
|
||||||
|
'Kimberly',
|
||||||
|
'Salli',
|
||||||
|
'Joey',
|
||||||
|
'Justin',
|
||||||
|
'Matthew',
|
||||||
|
"Russell",
|
||||||
|
"Nicole",
|
||||||
|
"Amy",
|
||||||
|
"Emma",
|
||||||
|
"Brian",
|
||||||
|
"Aditi",
|
||||||
|
"Raveena",
|
||||||
|
"Geraint"
|
||||||
|
]}
|
||||||
|
|
||||||
|
|
||||||
|
# valid voices https://lazypy.ro/tts/
|
||||||
|
|
||||||
|
|
||||||
|
class POLLY:
|
||||||
|
def __init__(self):
|
||||||
|
self.url = 'https://streamlabs.com/polly/speak'
|
||||||
|
|
||||||
|
def tts(
|
||||||
|
self,
|
||||||
|
req_text: str = "Amazon Text To Speech",
|
||||||
|
filename: str = "title.mp3",
|
||||||
|
random_speaker=False,
|
||||||
|
censer=False,
|
||||||
|
):
|
||||||
|
if random_speaker:
|
||||||
|
voice = self.randomvoice()
|
||||||
|
else:
|
||||||
|
if not os.getenv('VOICE'):
|
||||||
|
return ValueError('Please set the environment variable VOICE to a valid voice. options are: {}'.format(voices))
|
||||||
|
voice = str(os.getenv("VOICE")).capitalize()
|
||||||
|
body = {'voice': voice, 'text': req_text}
|
||||||
|
response = requests.post(self.url, data=body)
|
||||||
|
try:
|
||||||
|
voice_data = requests.get(response.json()['speak_url'])
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(voice_data.content)
|
||||||
|
except KeyError:
|
||||||
|
if response.json()['error'] == 'Text length is too long!':
|
||||||
|
chunks = [
|
||||||
|
m.group().strip() for m in re.finditer(r" *((.{0,530})(\.|.$))", req_text)
|
||||||
|
]
|
||||||
|
|
||||||
|
audio_clips = []
|
||||||
|
cbn = sox.Combiner()
|
||||||
|
|
||||||
|
chunkId = 0
|
||||||
|
for chunk in chunks:
|
||||||
|
body = {'voice': 'Brian', 'text': chunk}
|
||||||
|
resp = requests.post(self.url, data=body)
|
||||||
|
voice_data = requests.get(resp.json()['speak_url'])
|
||||||
|
with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out:
|
||||||
|
out.write(voice_data.content)
|
||||||
|
|
||||||
|
audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3"))
|
||||||
|
|
||||||
|
chunkId = chunkId + 1
|
||||||
|
try:
|
||||||
|
if len(audio_clips) > 1:
|
||||||
|
cbn.convert(samplerate=44100, n_channels=2)
|
||||||
|
cbn.build(audio_clips, filename, "concatenate")
|
||||||
|
else:
|
||||||
|
os.rename(audio_clips[0], filename)
|
||||||
|
except (sox.core.SoxError,
|
||||||
|
FileNotFoundError): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339
|
||||||
|
for clip in audio_clips:
|
||||||
|
i = audio_clips.index(clip) # get the index of the clip
|
||||||
|
audio_clips = (
|
||||||
|
audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1:]
|
||||||
|
) # replace the clip with an AudioFileClip
|
||||||
|
audio_concat = concatenate_audioclips(audio_clips)
|
||||||
|
audio_composite = CompositeAudioClip([audio_concat])
|
||||||
|
audio_composite.write_audiofile(filename, 44100, 2, 2000, None)
|
||||||
|
|
||||||
|
def make_readable(self, text):
|
||||||
|
"""
|
||||||
|
Amazon Polly fails to read some symbols properly such as '& (and)'.
|
||||||
|
So we normalize input text before passing it to the service
|
||||||
|
"""
|
||||||
|
text = text.replace('&', 'and')
|
||||||
|
return text
|
||||||
|
|
||||||
|
def randomvoice(self):
|
||||||
|
valid = voices['neural'] + voices['standard']
|
||||||
|
return random.choice(valid)
|
@ -0,0 +1,144 @@
|
|||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import sox
|
||||||
|
from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip
|
||||||
|
from moviepy.audio.io.AudioFileClip import AudioFileClip
|
||||||
|
from requests.adapters import HTTPAdapter, Retry
|
||||||
|
|
||||||
|
# from profanity_filter import ProfanityFilter
|
||||||
|
# pf = ProfanityFilter()
|
||||||
|
# Code by @JasonLovesDoggo
|
||||||
|
# https://twitter.com/scanlime/status/1512598559769702406
|
||||||
|
|
||||||
|
nonhuman = [ # DISNEY VOICES
|
||||||
|
"en_us_ghostface", # Ghost Face
|
||||||
|
"en_us_chewbacca", # Chewbacca
|
||||||
|
"en_us_c3po", # C3PO
|
||||||
|
"en_us_stitch", # Stitch
|
||||||
|
"en_us_stormtrooper", # Stormtrooper
|
||||||
|
"en_us_rocket", # Rocket
|
||||||
|
# ENGLISH VOICES
|
||||||
|
]
|
||||||
|
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
|
||||||
|
|
||||||
|
noneng = [
|
||||||
|
"fr_001", # French - Male 1
|
||||||
|
"fr_002", # French - Male 2
|
||||||
|
"de_001", # German - Female
|
||||||
|
"de_002", # German - Male
|
||||||
|
"es_002", # Spanish - Male
|
||||||
|
# AMERICA VOICES
|
||||||
|
"es_mx_002", # Spanish MX - Male
|
||||||
|
"br_001", # Portuguese BR - Female 1
|
||||||
|
"br_003", # Portuguese BR - Female 2
|
||||||
|
"br_004", # Portuguese BR - Female 3
|
||||||
|
"br_005", # Portuguese BR - Male
|
||||||
|
# ASIA VOICES
|
||||||
|
"id_001", # Indonesian - Female
|
||||||
|
"jp_001", # Japanese - Female 1
|
||||||
|
"jp_003", # Japanese - Female 2
|
||||||
|
"jp_005", # Japanese - Female 3
|
||||||
|
"jp_006", # Japanese - Male
|
||||||
|
"kr_002", # Korean - Male 1
|
||||||
|
"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
|
||||||
|
|
||||||
|
|
||||||
|
class TikTok: # 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,
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
|
||||||
|
chunks = [
|
||||||
|
m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)
|
||||||
|
]
|
||||||
|
|
||||||
|
audio_clips = []
|
||||||
|
cbn = sox.Combiner()
|
||||||
|
# cbn.set_input_format(file_type=["mp3" for _ in chunks])
|
||||||
|
|
||||||
|
chunkId = 0
|
||||||
|
for chunk in chunks:
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0"
|
||||||
|
)
|
||||||
|
except requests.exceptions.SSLError:
|
||||||
|
# https://stackoverflow.com/a/47475019/18516611
|
||||||
|
session = requests.Session()
|
||||||
|
retry = Retry(connect=3, backoff_factor=0.5)
|
||||||
|
adapter = HTTPAdapter(max_retries=retry)
|
||||||
|
session.mount("http://", adapter)
|
||||||
|
session.mount("https://", adapter)
|
||||||
|
r = session.post(
|
||||||
|
f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0"
|
||||||
|
)
|
||||||
|
print(r.text)
|
||||||
|
vstr = [r.json()["data"]["v_str"]][0]
|
||||||
|
b64d = base64.b64decode(vstr)
|
||||||
|
|
||||||
|
with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out:
|
||||||
|
out.write(b64d)
|
||||||
|
|
||||||
|
audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3"))
|
||||||
|
|
||||||
|
chunkId = chunkId + 1
|
||||||
|
try:
|
||||||
|
if len(audio_clips) > 1:
|
||||||
|
cbn.convert(samplerate=44100, n_channels=2)
|
||||||
|
cbn.build(audio_clips, filename, "concatenate")
|
||||||
|
else:
|
||||||
|
os.rename(audio_clips[0], filename)
|
||||||
|
except (sox.core.SoxError, FileNotFoundError): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339
|
||||||
|
for clip in audio_clips:
|
||||||
|
i = audio_clips.index(clip) # get the index of the clip
|
||||||
|
audio_clips = (
|
||||||
|
audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :]
|
||||||
|
) # replace the clip with an AudioFileClip
|
||||||
|
audio_concat = concatenate_audioclips(audio_clips)
|
||||||
|
audio_composite = CompositeAudioClip([audio_concat])
|
||||||
|
audio_composite.write_audiofile(filename, 44100, 2, 2000, None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def randomvoice():
|
||||||
|
ok_or_good = random.randrange(1, 10)
|
||||||
|
if ok_or_good == 1: # 1/10 chance of ok voice
|
||||||
|
return random.choice(voices)
|
||||||
|
return random.choice(human) # 9/10 chance of good voice
|
@ -0,0 +1,21 @@
|
|||||||
|
from os import getenv
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from TTS.GTTS import GTTS
|
||||||
|
from TTS.POLLY import POLLY
|
||||||
|
from TTS.TikTok import TikTok
|
||||||
|
|
||||||
|
CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, 'polly': POLLY}
|
||||||
|
|
||||||
|
|
||||||
|
class TTS:
|
||||||
|
def __new__(cls):
|
||||||
|
load_dotenv()
|
||||||
|
CHOICE = getenv("TTsChoice").casefold()
|
||||||
|
valid_keys = [key.lower() for key in CHOICE_DIR.keys()]
|
||||||
|
if CHOICE not in valid_keys:
|
||||||
|
raise ValueError(
|
||||||
|
f"{CHOICE} is not valid. Please use one of these {valid_keys} options"
|
||||||
|
)
|
||||||
|
return CHOICE_DIR.get(CHOICE)()
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,24 @@
|
|||||||
|
import os
|
||||||
|
from os.path import exists
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup() -> int:
|
||||||
|
if exists("./assets/temp"):
|
||||||
|
count = 0
|
||||||
|
files = [
|
||||||
|
f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower()
|
||||||
|
]
|
||||||
|
count += len(files)
|
||||||
|
for f in files:
|
||||||
|
os.remove(f)
|
||||||
|
try:
|
||||||
|
for file in os.listdir("./assets/temp/mp4"):
|
||||||
|
count += 1
|
||||||
|
os.remove("./assets/temp/mp4/" + file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
for file in os.listdir("./assets/temp/mp3"):
|
||||||
|
count += 1
|
||||||
|
os.remove("./assets/temp/mp3/" + file)
|
||||||
|
return count
|
||||||
|
return 0
|
@ -0,0 +1,40 @@
|
|||||||
|
# write a class that takes .env file and parses it into a dictionary
|
||||||
|
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
|
DEFAULTS = {
|
||||||
|
"SUBREDDIT": "AskReddit",
|
||||||
|
"ALLOW_NSFW": "False",
|
||||||
|
"POST_ID": "",
|
||||||
|
"THEME": "DARK",
|
||||||
|
"REDDIT_2FA": "no",
|
||||||
|
"TIMES_TO_RUN": "",
|
||||||
|
"MAX_COMMENT_LENGTH": "500",
|
||||||
|
"OPACITY": "1",
|
||||||
|
"VOICE": "en_us_001",
|
||||||
|
"STORYMODE": "False",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
def __init__(self):
|
||||||
|
self.raw = dotenv_values("../.env")
|
||||||
|
self.load_attrs()
|
||||||
|
|
||||||
|
def __getattr__(self, attr): # code completion for attributes fix.
|
||||||
|
return getattr(self, attr)
|
||||||
|
|
||||||
|
def load_attrs(self):
|
||||||
|
for key, value in self.raw.items():
|
||||||
|
self.add_attr(key, value)
|
||||||
|
|
||||||
|
def add_attr(self, key, value):
|
||||||
|
if value is None or value == "":
|
||||||
|
setattr(self, key, DEFAULTS[key])
|
||||||
|
else:
|
||||||
|
setattr(self, key, str(value))
|
||||||
|
|
||||||
|
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
print(config.SUBREDDIT)
|
@ -0,0 +1,32 @@
|
|||||||
|
from typing import List
|
||||||
|
import json
|
||||||
|
from os import getenv
|
||||||
|
from utils.console import print_substep
|
||||||
|
|
||||||
|
|
||||||
|
def get_subreddit_undone(submissions: List, subreddit):
|
||||||
|
"""
|
||||||
|
recursively checks if the top submission in the list was already done.
|
||||||
|
"""
|
||||||
|
with open("./video_creation/data/videos.json", "r") as done_vids_raw:
|
||||||
|
done_videos = json.load(done_vids_raw)
|
||||||
|
for submission in submissions:
|
||||||
|
if already_done(done_videos, submission):
|
||||||
|
continue
|
||||||
|
if submission.over_18:
|
||||||
|
if getenv("ALLOW_NSFW").casefold() == "false":
|
||||||
|
print_substep("NSFW Post Detected. Skipping...")
|
||||||
|
continue
|
||||||
|
return submission
|
||||||
|
print('all submissions have been done going by top submission order')
|
||||||
|
return get_subreddit_undone(
|
||||||
|
subreddit.top(time_filter="hour"), subreddit
|
||||||
|
) # all of the videos in hot have already been done
|
||||||
|
|
||||||
|
|
||||||
|
def already_done(done_videos: list, submission):
|
||||||
|
|
||||||
|
for video in done_videos:
|
||||||
|
if video["id"] == str(submission):
|
||||||
|
return True
|
||||||
|
return False
|
@ -0,0 +1,23 @@
|
|||||||
|
import json
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
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"""
|
||||||
|
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
|
@ -1,75 +1,63 @@
|
|||||||
#!/usr/bin/env python3
|
import random
|
||||||
from random import randrange
|
from os import listdir, environ
|
||||||
|
|
||||||
from yt_dlp import YoutubeDL
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from random import randrange
|
||||||
|
from pytube import YouTube
|
||||||
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
|
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
|
||||||
from moviepy.editor import VideoFileClip
|
from moviepy.editor import VideoFileClip
|
||||||
from utils.console import print_step, print_substep
|
from utils.console import print_step, print_substep
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
|
|
||||||
def get_start_and_end_times(video_length, length_of_clip):
|
def get_start_and_end_times(video_length, length_of_clip):
|
||||||
|
|
||||||
random_time = randrange(180, int(length_of_clip) - int(video_length))
|
random_time = randrange(180, int(length_of_clip) - int(video_length))
|
||||||
return random_time, random_time + video_length
|
return random_time, random_time + video_length
|
||||||
|
|
||||||
def download_background(video_length):
|
|
||||||
|
|
||||||
"""Downloads the background video from youtube.
|
|
||||||
|
|
||||||
Shoutout to: bbswitzer (https://www.youtube.com/watch?v=n_Dv4JMiwK8)
|
|
||||||
"""
|
|
||||||
|
|
||||||
print_substep("\nPut the URL of the video you want in the background.\nThe default video is a Minecraft parkour video.\n"
|
def download_background():
|
||||||
"Leave the input field blank to use the default.")
|
"""Downloads the backgrounds/s video from YouTube."""
|
||||||
print_substep(f"Make sure the video is longer than {str(datetime.timedelta(seconds=round(video_length + 180)))}!\n", style="red")
|
Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True)
|
||||||
|
background_options = [ # uri , filename , credit
|
||||||
inp = input("URL: ")
|
("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"),
|
||||||
|
# (
|
||||||
if not inp:
|
# "https://www.youtube.com/watch?v=2X9QGY__0II",
|
||||||
vidurl = "https://www.youtube.com/watch?v=n_Dv4JMiwK8"
|
# "rocket_league.mp4",
|
||||||
else:
|
# "Orbital Gameplay",
|
||||||
vidurl = inp
|
# ),
|
||||||
|
]
|
||||||
vidpath = vidurl.split("v=")[1]
|
# note: make sure the file name doesn't include an - in it
|
||||||
|
if not len(listdir("./assets/backgrounds")) >= len(
|
||||||
if not Path(f"assets/mp4/{vidpath}.mp4").is_file():
|
background_options
|
||||||
|
): # if there are any background videos not installed
|
||||||
print_step(
|
print_step(
|
||||||
"We need to download the background video. This may be fairly large but it's only done once per background."
|
"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 background video... please be patient.")
|
print_substep(
|
||||||
|
"Background videos downloaded successfully! 🎉", style="bold green"
|
||||||
ydl_opts = {
|
)
|
||||||
"outtmpl": f"assets/mp4/{vidpath}.mp4",
|
|
||||||
"merge_output_format": "mp4",
|
|
||||||
}
|
|
||||||
|
|
||||||
with YoutubeDL(ydl_opts) as ydl:
|
|
||||||
ydl.download(vidurl)
|
|
||||||
|
|
||||||
print_substep("Background video downloaded successfully!", style="bold green")
|
|
||||||
|
|
||||||
return vidpath
|
def chop_background_video(video_length):
|
||||||
|
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}")
|
||||||
|
|
||||||
def chop_background_video(video_length, vidpath):
|
|
||||||
print_step("Finding a spot in the background video to chop...")
|
|
||||||
background = VideoFileClip(f"assets/mp4/{vidpath}.mp4")
|
|
||||||
if background.duration < video_length + 180:
|
|
||||||
print_substep("This video is too short.", style="red")
|
|
||||||
noerror = False
|
|
||||||
return noerror
|
|
||||||
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(
|
ffmpeg_extract_subclip(
|
||||||
f"assets/mp4/{vidpath}.mp4",
|
f"assets/backgrounds/{choice}",
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
targetname="assets/mp4/clip.mp4",
|
targetname="assets/temp/background.mp4",
|
||||||
)
|
)
|
||||||
print_substep("Background video chopped successfully!", style="bold green")
|
print_substep("Background video chopped successfully!", style="bold green")
|
||||||
noerror = True
|
return True
|
||||||
return noerror
|
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
videos.json
|
||||||
|
#todo add videos on github
|
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "USER",
|
||||||
|
"value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19",
|
||||||
|
"domain": ".reddit.com",
|
||||||
|
"path": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eu_cookie",
|
||||||
|
"value": "{%22opted%22:true%2C%22nonessential%22:false}",
|
||||||
|
"domain": ".reddit.com",
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "eu_cookie",
|
||||||
|
"value": "{%22opted%22:true%2C%22nonessential%22:false}",
|
||||||
|
"domain": ".reddit.com",
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1 @@
|
|||||||
|
[]
|
Loading…
Reference in new issue