merged develop

pull/963/head
Drugsosos 2 years ago
commit 62ac8fe0b0
No known key found for this signature in database
GPG Key ID: 8E35176FE617E28D

@ -12,11 +12,11 @@ password = { optional = false, nmin = 8, explanation = "the password of your red
random = { optional = true, options = [true,
false,
], default = false, type = "bool", explanation = "If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: 'False'", example = "True" }
subreddit = { optional = false, regex = "[_0-9a-zA-Z]+$", nmin = 3, nmax = 21, explanation = "what subreddit to pull posts from, the name of the sub, not the URL", example = "AskReddit", oob_error = "A subreddit name HAS to be between 3 and 20 characters" }
subreddit = { optional = false, regex = "[_0-9a-zA-Z]+$", nmin = 3, explanation = "what subreddit to pull posts from, the name of the sub, not the URL", example = "AskReddit", oob_error = "A subreddit name HAS to be between 3 and 20 characters" }
post_id = { optional = true, default = "", regex = "^((?!://|://)[+a-zA-Z])*$", explanation = "Used if you want to use a specific post.", example = "urdtfx" }
max_comment_length = { default = 500, optional = false, nmin = 10, nmax = 10000, type = "int", explanation = "max number of characters a comment can have. default is 500", example = 500, oob_error = "the max comment length should be between 10 and 10000" }
post_lang = { default = "", optional = true, explanation = "The language you would like to translate to.", example = "es-cr" }
min_comments = { default = 20, optional = false, nmin = 15, type = "int", explanation = "The minimum number of comments a post should have to be included. default is 20", example = 29, oob_error = "the minimum number of comments should be between 15 and 999999" }
[settings]
allow_nsfw = { optional = false, type = "bool", default = false, example = false, options = [true,
false,
@ -29,10 +29,15 @@ opacity = { optional = false, default = 0.9, example = 0.8, explanation = "Sets
storymode = { optional = true, type = "bool", default = false, example = false, options = [true,
false,
], explanation = "not yet implemented" }
[settings.background]
background_choice = { optional = true, default = "minecraft", example = "minecraft", options = ["minecraft", "gta", "rocket-league", "motor-gta", ""], explanation = "Sets the background for the video" }
background_audio = { optional = true, type = "bool", default = false, example = false, options = [true,
false,
], explaination="Sets a audio to play in the background (put a background.mp3 file in the assets/backgrounds directory for it to be used.)" }
#background_audio = { optional = true, type = "bool", default = false, example = false, options = [true,
# false,
#], explaination="Sets a audio to play in the background (put a background.mp3 file in the assets/backgrounds directory for it to be used.)" }
#background_audio_volume = { optional = true, type = "float", default = 0.3, example = 0.1, explanation="Sets the volume of the background audio. only used if the background_audio is also set to true" }
[settings.tts]
choice = { optional = false, default = "", options = ["streamlabspolly", "tiktok", "googletranslate", "awspolly", ], example = "streamlabspolly", explanation = "The backend used for TTS generation. This can be left blank and you will be prompted to choose at runtime." }
aws_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for AWS Polly" }

@ -0,0 +1,281 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="cache-control" content="no-cache"/>
<title>RedditVideoMakerBot</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/5.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.3/font/bootstrap-icons.css">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
.b-example-divider {
height: 3rem;
background-color: rgba(0, 0, 0, .1);
border: solid rgba(0, 0, 0, .15);
border-width: 1px 0;
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
}
.b-example-vr {
flex-shrink: 0;
width: 1.5rem;
height: 100vh;
}
.bi {
vertical-align: -.125em;
fill: currentColor;
}
.nav-scroller {
position: relative;
z-index: 2;
height: 2.75rem;
overflow-y: hidden;
}
.nav-scroller .nav {
display: flex;
flex-wrap: nowrap;
padding-bottom: 1rem;
margin-top: -1px;
overflow-x: auto;
text-align: center;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
#tooltip {
background-color: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 13px;
}
</style>
</head>
<body>
<header>
<div class="navbar navbar-dark bg-dark shadow-sm">
<div class="container">
<a href="#" class="navbar-brand d-flex align-items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true" class="me-2" viewBox="0 0 24 24"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
<strong>RedditVideoMakerBot</strong>
</a>
</div>
</div>
</header>
<main>
<div class="album py-2 bg-light">
<div class="container">
<div class="row mt-2">
<div class="col-12 col-md-3 mb-3">
<input type="text" class="form-control" placeholder="Search videos" aria-label="Search videos" onkeyup="searchFilter()">
</div>
</div>
<div class="grid row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3" id="videos">
</div>
</div>
</div>
</main>
<footer class="text-muted py-5">
<div class="container">
<p class="float-end mb-1">
<a href="#">Back to top</a>
</p>
<p class="mb-1"><a href="https://getbootstrap.com/docs/5.2/examples/album/" target="_blank">Album</a> Example Theme by &copy; Bootstrap. <a href="https://github.com/elebumm/RedditVideoMakerBot/blob/master/README.md#developers-and-maintainers" target="_blank">Developers and Maintainers</a></p>
<p class="mb-0">If your data is not refreshing, try to hard reload(Ctrl + F5) and visit your local <a href="../video_creation/data/videos.json" target="_blank">videos.json</a> file.</p>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.1.1.js" integrity="sha256-16cdPddA6VdVInumRGo6IbivbERE8p7CQR3HzTBuELA=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.3/dist/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.10/clipboard.min.js"></script>
<script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.js"></script>
<script>
const intervals = [
{ label: 'year', seconds: 31536000 },
{ label: 'month', seconds: 2592000 },
{ label: 'day', seconds: 86400 },
{ label: 'hour', seconds: 3600 },
{ label: 'minute', seconds: 60 },
{ label: 'second', seconds: 1 }
];
function timeSince(date) {
const seconds = Math.floor((Date.now() / 1000 - date));
const interval = intervals.find(i => i.seconds < seconds);
const count = Math.floor(seconds / interval.seconds);
return `${count} ${interval.label}${count !== 1 ? 's' : ''} ago`;
}
$(document).ready(function () {
$.getJSON("../video_creation/data/videos.json",
function (data) {
data.sort((b, a) => a['time'] - b['time'])
var video = '';
$.each(data, function (key, value) {
video += '<div class="col">';
video += '<div class="card shadow-sm">';
//keeping original themed image card for future thumbnail usage video += '<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">r/'+value.subreddit+'</text></svg>';
video += '<div class="card-body">';
video += '<p class="card-text">r/'+value.subreddit+' • '+checkTitle(value.reddit_title, value.filename)+'</p>';
video += '<div class="d-flex justify-content-between align-items-center">';
video += '<div class="btn-group">';
video += '<a href="https://www.reddit.com/r/'+value.subreddit+'/comments/'+value.id+'/" class="btn btn-sm btn-outline-secondary" target="_blank">View</a>';
video += '<a href="results/'+value.subreddit+'/'+value.filename+'" class="btn btn-sm btn-outline-secondary" download>Download</a>';
video += '</div>';
video += '<div class="btn-group">';
video += '<button type="button" data-toggle="tooltip" id="copy" data-original-title="Copy to clipboard" class="btn btn-sm btn-outline-secondary" data-clipboard-text="'+getCopyData(value.subreddit, value.reddit_title, value.filename, value.background_credit)+'"><i class="bi bi-card-text"></i></button>';
video += '<button type="button" data-toggle="tooltip" id="copy" data-original-title="Copy to clipboard" class="btn btn-sm btn-outline-secondary" data-clipboard-text="'+checkTitle(value.reddit_title, value.filename)+' #Shorts #reddit"><i class="bi bi-youtube"></i></button>';
video += '<button type="button" data-toggle="tooltip" id="copy" data-original-title="Copy to clipboard" class="btn btn-sm btn-outline-secondary" data-clipboard-text="'+checkTitle(value.reddit_title, value.filename)+' #reddit"><i class="bi bi-instagram"></i></button>';
video += '</div>';
video += '<small class="text-muted">'+timeSince(value.time)+'</small>';
video += '</div>';
video += '</div>';
video += '</div>';
video += '</div>';
});
$('#videos').append(video);
});
});
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="tooltip"]').on('click', function(){
$(this).tooltip('hide');
});
});
$('#copy').tooltip({
trigger: 'click',
placement: 'bottom'
});
function setTooltip(btn, message) {
$(btn).tooltip('hide')
.attr('data-original-title', message)
.tooltip('show');
}
function hoverTooltip(btn, message) {
$(btn).tooltip('hide')
.attr('data-original-title', message)
.tooltip('show');
}
function hideTooltip(btn) {
setTimeout(function() {
$(btn).tooltip('hide');
}, 1000);
}
function disposeTooltip(btn) {
setTimeout(function() {
$(btn).tooltip('dispose');
}, 1500);
}
var clipboard = new ClipboardJS('#copy');
clipboard.on('success', function(e) {
e.clearSelection();
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
setTooltip(e.trigger, 'Copied!');
hideTooltip(e.trigger);
disposeTooltip(e.trigger);
});
clipboard.on('error', function(e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
setTooltip(e.trigger, fallbackMessage(e.action));
hideTooltip(e.trigger);
});
function getCopyData(subreddit, reddit_title, filename, background_credit){
if (subreddit == undefined) {
subredditCopy = "";
} else {
subredditCopy = "r/" + subreddit + "\n\n";
}
const file = filename.slice(0, -4);
if (reddit_title == file) {
titleCopy = reddit_title;
} else {
titleCopy = file;
}
var copyData = "";
copyData += subredditCopy;
copyData += titleCopy;
copyData += "\n\nBackground credit: " + background_credit;
return copyData;
}
function getLink(subreddit, id, reddit_title) {
if (subreddit == undefined) {
return reddit_title;
} else {
return "<a target='_blank' href='https://www.reddit.com/r/" + subreddit + "/comments/" + id + "/'>"+ reddit_title +"</a>";
}
}
function checkTitle(reddit_title, filename) {
const file = filename.slice(0, -4);
if (reddit_title == file) {
return reddit_title;
} else {
return file;
}
}
var searchFilter = () => {
const input = document.querySelector(".form-control");
const cards = document.getElementsByClassName("col");
console.log(cards[1])
let filter = input.value
for (let i = 0; i < cards.length; i++) {
let title = cards[i].querySelector(".card-text");
if (title.innerText.toLowerCase().indexOf(filter.toLowerCase()) > -1) {
cards[i].classList.remove("d-none")
} else {
cards[i].classList.add("d-none")
}
}
}
</script>
</body>
</html>

@ -88,3 +88,5 @@ CallumIO (c.#6837) - https://github.com/CallumIO
Verq (Verq#2338) - https://github.com/CordlessCoder
LukaHietala (Pix.#0001) - https://github.com/LukaHietala
Freebiell (Freebie#6429) - https://github.com/FreebieII

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from botocore.exceptions import BotoCoreError, ClientError, ProfileNotFound
import sys
from utils import settings
import random
@ -30,36 +30,46 @@ class AWSPolly:
self.voices = voices
def run(self, text, filepath, random_voice: bool = False):
session = Session(profile_name="polly")
polly = session.client("polly")
if random_voice:
voice = self.randomvoice()
else:
if not settings.config["settings"]["tts"]["aws_polly_voice"]:
return ValueError(
f"Please set the TOML variable AWS_VOICE to a valid voice. options are: {voices}"
)
voice = str(settings.config["settings"]["tts"]["aws_polly_voice"]).capitalize()
try:
# Request speech synthesis
response = polly.synthesize_speech(
Text=text, OutputFormat="mp3", VoiceId=voice, Engine="neural"
)
except (BotoCoreError, ClientError) as error:
# The service returned an error, exit gracefully
print(error)
sys.exit(-1)
session = Session(profile_name="polly")
polly = session.client("polly")
if random_voice:
voice = self.randomvoice()
else:
if not settings.config["settings"]["tts"]["aws_polly_voice"]:
raise ValueError(
f"Please set the TOML variable AWS_VOICE to a valid voice. options are: {voices}"
)
voice = str(settings.config["settings"]["tts"]["aws_polly_voice"]).capitalize()
try:
# Request speech synthesis
response = polly.synthesize_speech(
Text=text, OutputFormat="mp3", VoiceId=voice, Engine="neural"
)
except (BotoCoreError, ClientError) as error:
# The service returned an error, exit gracefully
print(error)
sys.exit(-1)
# Access the audio stream from the response
if "AudioStream" in response:
file = open(filepath, "wb")
file.write(response["AudioStream"].read())
file.close()
# print_substep(f"Saved Text {idx} to MP3 files successfully.", style="bold green")
# Access the audio stream from the response
if "AudioStream" in response:
file = open(filepath, "wb")
file.write(response["AudioStream"].read())
file.close()
# print_substep(f"Saved Text {idx} to MP3 files successfully.", style="bold green")
else:
# The response didn't contain audio data, exit gracefully
print("Could not stream audio")
else:
# The response didn't contain audio data, exit gracefully
print("Could not stream audio")
sys.exit(-1)
except ProfileNotFound:
print("You need to install the AWS CLI and configure your profile")
print(
"""
Linux: https://docs.aws.amazon.com/polly/latest/dg/setup-aws-cli.html
Windows: https://docs.aws.amazon.com/polly/latest/dg/install-voice-plugin2.html
"""
)
sys.exit(-1)
def randomvoice(self):

@ -82,12 +82,16 @@ class TTSEngine:
r" *(((.|\n){0," + str(self.tts_module().max_chars) + "})(\.|.$))", text
)
]
idy = None
offset = 0
for idy, text_cut in enumerate(split_text):
# print(f"{idx}-{idy}: {text_cut}\n")
self.call_tts(f"{idx}-{idy}.part", text_cut)
split_files.append(AudioFileClip(f"{self.path}/{idx}-{idy}.part.mp3"))
if not text_cut or text_cut.isspace():
offset += 1
continue
self.call_tts(f"{idx}-{idy - offset}.part", text_cut)
split_files.append(AudioFileClip(f"{self.path}/{idx}-{idy - offset}.part.mp3"))
CompositeAudioClip([concatenate_audioclips(split_files)]).write_audiofile(
f"{self.path}/{idx}.mp3", fps=44100, verbose=False, logger=None
)

@ -37,8 +37,8 @@ class StreamlabsPolly:
voice = self.randomvoice()
else:
if not settings.config["settings"]["tts"]["streamlabs_polly_voice"]:
return ValueError(
f"Please set the config variable STREAMLABS_VOICE to a valid voice. options are: {voices}"
raise ValueError(
f"Please set the config variable STREAMLABS_POLLY_VOICE to a valid voice. options are: {voices}"
)
voice = str(settings.config["settings"]["tts"]["streamlabs_polly_voice"]).capitalize()
body = {"voice": voice, "text": text, "service": "polly"}

@ -20,7 +20,7 @@ def get_subreddit_threads(
print_substep("Logging into Reddit.")
content = {}
if settings.config["reddit"]["creds"]["2fa"] == True:
if settings.config["reddit"]["creds"]["2fa"]:
print("\nEnter your two-factor authentication code from your authenticator app.\n")
code = input("> ")
print()
@ -29,7 +29,7 @@ def get_subreddit_threads(
else:
passkey = settings.config["reddit"]["creds"]["password"]
username = settings.config["reddit"]["creds"]["username"]
if username.casefold().startswith("u/"):
if str(username).casefold().startswith("u/"):
username = username[2:]
reddit = praw.Reddit(
client_id=settings.config["reddit"]["creds"]["client_id"],
@ -57,7 +57,7 @@ def get_subreddit_threads(
sub = settings.config["reddit"]["thread"]["subreddit"]
print_substep(f"Using subreddit: r/{sub} from TOML config")
subreddit_choice = sub
if subreddit_choice.casefold().startswith("r/"): # removes the r/ from the input
if str(subreddit_choice).casefold().startswith("r/"): # removes the r/ from the input
subreddit_choice = subreddit_choice[2:]
subreddit = reddit.subreddit(
subreddit_choice
@ -67,11 +67,10 @@ def get_subreddit_threads(
submission = reddit.submission(id=POST_ID)
elif (
settings.config["reddit"]["thread"]["post_id"]
and len(settings.config["reddit"]["thread"]["post_id"].split("+")) == 1
and len(str(settings.config["reddit"]["thread"]["post_id"]).split("+")) == 1
):
submission = reddit.submission(id=settings.config["reddit"]["thread"]["post_id"])
else:
threads = subreddit.hot(limit=25)
submission = get_subreddit_undone(threads, subreddit)
submission = check_done(submission) # double-checking
@ -107,6 +106,7 @@ def get_subreddit_threads(
):
if (
top_level_comment.author is not None
and sanitize_text(top_level_comment.body) is not None
): # if errors occur with this change to if not.
content["comments"].append(
{

@ -5,7 +5,7 @@ moviepy==1.0.3
praw==7.6.0
pytube==12.1.0
requests==2.28.1
rich==12.4.4
rich==12.5.1
toml==0.10.2
translators==5.3.1
pyppeteer==1.0.2

@ -34,11 +34,16 @@ def get_subreddit_undone(submissions: list, subreddit):
if submission.stickied:
print_substep("This post was pinned by moderators. Skipping...")
continue
if submission.num_comments <= int(settings.config["reddit"]["thread"]["min_comments"]):
print_substep(
f'This post has under the specified minimum of comments ({settings.config["reddit"]["thread"]["min_comments"]}). 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
) # all the videos in hot have already been done
def already_done(done_videos: list, submission) -> bool:

@ -36,7 +36,7 @@ def sleep_until(time):
# Convert datetime to unix timestamp and adjust for locality
if isinstance(time, datetime):
# If we're on Python 3 and the user specified a timezone, convert to UTC and get tje timestamp.
# If we're on Python 3 and the user specified a timezone, convert to UTC and get the timestamp.
if sys.version_info[0] >= 3 and time.tzinfo:
end = time.astimezone(timezone.utc).timestamp()
else:

@ -30,7 +30,7 @@ background_options = {
"https://www.youtube.com/watch?v=2X9QGY__0II",
"rocket_league.mp4",
"Orbital Gameplay",
"top",
lambda t: ("center", 200 + t),
),
"minecraft": ( # Minecraft parkour
"https://www.youtube.com/watch?v=n_Dv4JMiwK8",
@ -64,7 +64,7 @@ def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int
def get_background_config():
"""Fetch the background/s configuration"""
try:
choice = str(settings.config["settings"]["background_choice"]).casefold()
choice = str(settings.config["settings"]["background"]["background_choice"]).casefold()
except AttributeError:
print_substep("No background selected. Picking random background'")
choice = None

@ -5,8 +5,6 @@ import re
from os.path import exists
from typing import Tuple, Any
import translators as ts
from moviepy.editor import (
VideoFileClip,
AudioFileClip,
@ -44,6 +42,8 @@ def name_normalize(
lang = settings.config['reddit']['thread']['post_lang']
if lang:
import translators as ts
print_substep('Translating filename...')
translated_name = ts.google(name, to_language=lang)
return translated_name
@ -196,38 +196,13 @@ def make_final_video(
verbose=False,
threads=multiprocessing.cpu_count(),
)
if settings.config['settings']['background_audio']:
print('[bold green] Merging background audio with video')
if not exists('assets/backgrounds/background.mp3'):
print_substep(
'Cannot find assets/backgrounds/background.mp3 audio file didn\'t so skipping.'
)
ffmpeg_extract_subclip(
'assets/temp/temp.mp4',
0,
video_duration,
targetname=f'results/{subreddit}/{filename}',
)
else:
ffmpeg_merge_video_audio(
'assets/temp/temp.mp4',
'assets/backgrounds/background.mp3',
'assets/temp/temp_audio.mp4',
)
ffmpeg_extract_subclip( # check if this gets run
'assets/temp/temp_audio.mp4',
0,
video_duration,
targetname=f'results/{subreddit}/{filename}',
)
else:
print('debug duck')
ffmpeg_extract_subclip(
'assets/temp/temp.mp4',
0,
video_duration,
targetname=f'results/{subreddit}/{filename}',
)
ffmpeg_extract_subclip(
"assets/temp/temp.mp4",
0,
video_duration,
targetname=f'results/{subreddit}/{filename}',
)
save_data(subreddit, filename, title, idx, background_config[2])
print_step('Removing temporary files 🗑')
cleanups = cleanup()
print_substep(f'Removed {cleanups} temporary files 🗑')

@ -29,7 +29,7 @@ def save_text_to_mp3(
"""
voice = settings.config['settings']['tts']['choice']
if voice.casefold() not in map(lambda _: _.casefold(), TTSProviders):
if str(voice).casefold() not in map(lambda _: _.casefold(), TTSProviders):
while True:
print_step('Please choose one of the following TTS providers: ')
print_table(TTSProviders)

Loading…
Cancel
Save