pull/2395/head
parent
7a9fe4bf00
commit
2ff61ec4ab
@ -0,0 +1,291 @@
|
|||||||
|
import math
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from os import name
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import Popen
|
||||||
|
from typing import NoReturn
|
||||||
|
|
||||||
|
import praw
|
||||||
|
from prawcore.exceptions import ResponseException
|
||||||
|
|
||||||
|
from reddit.subreddit import get_subreddit_threads
|
||||||
|
from utils import settings
|
||||||
|
from utils.cleanup import cleanup
|
||||||
|
from utils.console import print_markdown, print_step, print_substep
|
||||||
|
from utils.ffmpeg_install import ffmpeg_install
|
||||||
|
from utils.version import checkversion
|
||||||
|
from video_creation.background import download_background_audio, download_background_video
|
||||||
|
from video_creation.final_video import make_final_video
|
||||||
|
from video_creation.screenshot_downloader import get_screenshots_of_reddit_posts
|
||||||
|
from video_creation.voices import save_text_to_mp3
|
||||||
|
from video_creation.background import chop_background, get_background_config
|
||||||
|
|
||||||
|
__VERSION__ = "3.3.0"
|
||||||
|
|
||||||
|
print_markdown(
|
||||||
|
"""# Reddit Video Maker Bot
|
||||||
|
|
||||||
|
██████╗ ███████╗██████╗ ██████╗ ██╗████████╗ ██╗ ██╗██╗██████╗ ███████╗ ██████╗ ███╗ ███╗ █████╗
|
||||||
|
██╔══██╗██╔════╝██╔══██╗██╔══██╗██║╚══██╔══╝ ██║ ██║██║██╔══██╗██╔════╝██╔═══██╗ ████╗ ████║██╔══██╗
|
||||||
|
██║ ██╔╝██╔════╝██╔══██╗██████╔╝█████╗ ██║ ██║██║ ██║██║██║ ██║█████╗ ██║ ██║ ██╔████╔██║███████║
|
||||||
|
█████╔╝ █████╗ ██████╔╝██████╔╝██╔══╝ ██║ ██║██║ ██║██║██║ ██║██╔══╝ ██║ ██║ ██║╚██╔╝██║██╔══██║
|
||||||
|
██╔═██╗ ██╔══╝ ██╔══██╗██╔═██╗ ██╔══╝ ██║ ██║╚██╗ ██╔╝██║██║ ██║███████╗╚██████╔╝ ██║ ╚═╝ ██║██║ ██║
|
||||||
|
██║ ██╗███████╗██║ ██║██║ ██╗███████╗██║ ██║ ╚████╔╝ ██║██████╔╝╚══════╝ ╚═════╝ ██║ ██║╚═╝ ██║
|
||||||
|
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
|
||||||
|
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝"""
|
||||||
|
)
|
||||||
|
print_markdown(
|
||||||
|
"### Thanks for using this tool! Feel free to contribute to this project on GitHub! If you have any questions, feel free to join my Discord server or submit a GitHub issue. You can find solutions to many common problems in the documentation: https://reddit-video-maker-bot.netlify.app/"
|
||||||
|
)
|
||||||
|
checkversion(__VERSION__)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_post_id_from_url(url: str) -> str:
|
||||||
|
"""
|
||||||
|
Extract the post ID from a Reddit URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): Reddit post URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Post ID
|
||||||
|
"""
|
||||||
|
# Handle different Reddit URL formats
|
||||||
|
patterns = [
|
||||||
|
r'reddit\.com/r/\w+/comments/(\w+)/', # Standard format
|
||||||
|
r'reddit\.com/comments/(\w+)/', # Short format
|
||||||
|
r'reddit\.com/r/\w+/comments/(\w+)', # Without trailing slash
|
||||||
|
r'reddit\.com/comments/(\w+)', # Short format without trailing slash
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
match = re.search(pattern, url)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
raise ValueError("Invalid Reddit URL format. Please provide a valid Reddit post URL.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_post_from_url(url: str) -> dict:
|
||||||
|
"""
|
||||||
|
Get Reddit post data from a specific URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): Reddit post URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Reddit post data
|
||||||
|
"""
|
||||||
|
print_substep("Extracting post ID from URL...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
post_id = extract_post_id_from_url(url)
|
||||||
|
print_substep(f"Post ID extracted: {post_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
print_substep(f"Error: {e}", style="red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print_substep("Logging into Reddit...")
|
||||||
|
|
||||||
|
# Setup Reddit authentication
|
||||||
|
if settings.config["reddit"]["creds"]["2fa"]:
|
||||||
|
print("\nEnter your two-factor authentication code from your authenticator app.\n")
|
||||||
|
code = input("> ")
|
||||||
|
print()
|
||||||
|
pw = settings.config["reddit"]["creds"]["password"]
|
||||||
|
passkey = f"{pw}:{code}"
|
||||||
|
else:
|
||||||
|
passkey = settings.config["reddit"]["creds"]["password"]
|
||||||
|
|
||||||
|
username = settings.config["reddit"]["creds"]["username"]
|
||||||
|
if str(username).casefold().startswith("u/"):
|
||||||
|
username = username[2:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
reddit = praw.Reddit(
|
||||||
|
client_id=settings.config["reddit"]["creds"]["client_id"],
|
||||||
|
client_secret=settings.config["reddit"]["creds"]["client_secret"],
|
||||||
|
user_agent="Accessing Reddit threads",
|
||||||
|
username=username,
|
||||||
|
passkey=passkey,
|
||||||
|
check_for_async=False,
|
||||||
|
)
|
||||||
|
except ResponseException as e:
|
||||||
|
if e.response.status_code == 401:
|
||||||
|
print("Invalid credentials - please check them in config.toml")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Something went wrong: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print_substep("Fetching post data...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
submission = reddit.submission(id=post_id)
|
||||||
|
# Force the submission to load all data
|
||||||
|
submission.title
|
||||||
|
submission.selftext
|
||||||
|
submission.score
|
||||||
|
submission.upvote_ratio
|
||||||
|
submission.num_comments
|
||||||
|
submission.permalink
|
||||||
|
submission.over_18
|
||||||
|
submission.is_self
|
||||||
|
except Exception as e:
|
||||||
|
print_substep(f"Error fetching post: {e}", style="red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if post is NSFW
|
||||||
|
if submission.over_18 and not settings.config["settings"]["allow_nsfw"]:
|
||||||
|
print_substep("This post is NSFW and NSFW content is not allowed in your config.", style="red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if post has content for story mode
|
||||||
|
if settings.config["settings"]["storymode"]:
|
||||||
|
if not submission.selftext:
|
||||||
|
print_substep("This post has no text content for story mode.", style="red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if len(submission.selftext) > settings.config["settings"]["storymode_max_length"]:
|
||||||
|
print_substep(f"Post is too long ({len(submission.selftext)} characters). Max allowed: {settings.config['settings']['storymode_max_length']}", style="red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if len(submission.selftext) < 30:
|
||||||
|
print_substep("Post is too short (less than 30 characters).", style="red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Build content dictionary
|
||||||
|
content = {
|
||||||
|
"thread_url": f"https://new.reddit.com{submission.permalink}",
|
||||||
|
"thread_title": submission.title,
|
||||||
|
"thread_id": submission.id,
|
||||||
|
"is_nsfw": submission.over_18,
|
||||||
|
"comments": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process content based on story mode
|
||||||
|
if settings.config["settings"]["storymode"]:
|
||||||
|
if settings.config["settings"]["storymodemethod"] == 1:
|
||||||
|
from utils.posttextparser import posttextparser
|
||||||
|
content["thread_post"] = posttextparser(submission.selftext)
|
||||||
|
else:
|
||||||
|
content["thread_post"] = submission.selftext
|
||||||
|
else:
|
||||||
|
# Process comments (not used in story mode)
|
||||||
|
for top_level_comment in submission.comments:
|
||||||
|
if hasattr(top_level_comment, 'body') and top_level_comment.body not in ["[removed]", "[deleted]"]:
|
||||||
|
if not top_level_comment.stickied:
|
||||||
|
from utils.voice import sanitize_text
|
||||||
|
sanitised = sanitize_text(top_level_comment.body)
|
||||||
|
if sanitised and sanitised != " ":
|
||||||
|
if len(top_level_comment.body) <= int(settings.config["reddit"]["thread"]["max_comment_length"]):
|
||||||
|
if len(top_level_comment.body) >= int(settings.config["reddit"]["thread"]["min_comment_length"]):
|
||||||
|
if top_level_comment.author is not None:
|
||||||
|
content["comments"].append({
|
||||||
|
"comment_body": top_level_comment.body,
|
||||||
|
"comment_url": top_level_comment.permalink,
|
||||||
|
"comment_id": top_level_comment.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Display post information
|
||||||
|
print_substep(f"Video will be: {submission.title} 👍", style="bold green")
|
||||||
|
print_substep(f"Thread url is: {content['thread_url']} 👍", style="bold green")
|
||||||
|
print_substep(f"Thread has {submission.score} upvotes", style="bold blue")
|
||||||
|
print_substep(f"Thread has a upvote ratio of {submission.upvote_ratio * 100}%", style="bold blue")
|
||||||
|
print_substep(f"Thread has {submission.num_comments} comments", style="bold blue")
|
||||||
|
|
||||||
|
if settings.config["settings"]["storymode"]:
|
||||||
|
print_substep(f"Post content length: {len(submission.selftext)} characters", style="bold blue")
|
||||||
|
|
||||||
|
print_substep("Post data fetched successfully.", style="bold green")
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main function to process a specific Reddit post URL."""
|
||||||
|
global redditid, reddit_object
|
||||||
|
|
||||||
|
print_step("Reddit Video Maker Bot - Target Mode")
|
||||||
|
print_substep("This mode allows you to create a video from a specific Reddit post URL.")
|
||||||
|
|
||||||
|
# Get URL from user
|
||||||
|
while True:
|
||||||
|
url = input("\nEnter the Reddit post URL: ").strip()
|
||||||
|
if url:
|
||||||
|
break
|
||||||
|
print_substep("Please enter a valid URL.", style="red")
|
||||||
|
|
||||||
|
# Get post data
|
||||||
|
reddit_object = get_post_from_url(url)
|
||||||
|
redditid = id(reddit_object)
|
||||||
|
|
||||||
|
# Process the post
|
||||||
|
length, number_of_comments = save_text_to_mp3(reddit_object)
|
||||||
|
length = math.ceil(length)
|
||||||
|
|
||||||
|
get_screenshots_of_reddit_posts(reddit_object, number_of_comments)
|
||||||
|
|
||||||
|
bg_config = {
|
||||||
|
"video": get_background_config("video"),
|
||||||
|
"audio": get_background_config("audio"),
|
||||||
|
}
|
||||||
|
|
||||||
|
download_background_video(bg_config["video"])
|
||||||
|
download_background_audio(bg_config["audio"])
|
||||||
|
chop_background(bg_config, length, reddit_object)
|
||||||
|
make_final_video(number_of_comments, length, reddit_object, bg_config)
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown() -> NoReturn:
|
||||||
|
"""Cleanup and exit."""
|
||||||
|
if "redditid" in globals():
|
||||||
|
print_markdown("## Clearing temp files")
|
||||||
|
cleanup(redditid)
|
||||||
|
|
||||||
|
print("Exiting...")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if sys.version_info.major != 3 or sys.version_info.minor not in [10, 11, 12, 13]:
|
||||||
|
print(
|
||||||
|
"Hey! Congratulations, you've made it so far (which is pretty rare with no Python 3.10). Unfortunately, this program only works on Python 3.10+. Please install Python 3.10+ and try again."
|
||||||
|
)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
ffmpeg_install()
|
||||||
|
directory = Path().absolute()
|
||||||
|
config = settings.check_toml(
|
||||||
|
f"{directory}/utils/.config.template.toml", f"{directory}/config.toml"
|
||||||
|
)
|
||||||
|
config is False and sys.exit()
|
||||||
|
|
||||||
|
if (
|
||||||
|
not settings.config["settings"]["tts"]["tiktok_sessionid"]
|
||||||
|
or settings.config["settings"]["tts"]["tiktok_sessionid"] == ""
|
||||||
|
) and config["settings"]["tts"]["voice_choice"] == "tiktok":
|
||||||
|
print_substep(
|
||||||
|
"TikTok voice requires a sessionid! Check our documentation on how to obtain one.",
|
||||||
|
"bold red",
|
||||||
|
)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
shutdown()
|
||||||
|
except ResponseException:
|
||||||
|
print_markdown("## Invalid credentials")
|
||||||
|
print_markdown("Please check your credentials in the config.toml file")
|
||||||
|
shutdown()
|
||||||
|
except Exception as err:
|
||||||
|
config["settings"]["tts"]["tiktok_sessionid"] = "REDACTED"
|
||||||
|
config["settings"]["tts"]["elevenlabs_api_key"] = "REDACTED"
|
||||||
|
print_step(
|
||||||
|
f"Sorry, something went wrong with this version! Try again, and feel free to report this issue at GitHub or the Discord community.\n"
|
||||||
|
f"Version: {__VERSION__} \n"
|
||||||
|
f"Error: {err} \n"
|
||||||
|
f'Config: {config["settings"]}'
|
||||||
|
)
|
||||||
|
raise err
|
Loading…
Reference in new issue