From 98aaffd8657020ccd71766033720f77dce348ba9 Mon Sep 17 00:00:00 2001 From: Mohamed Moataz Date: Thu, 28 Mar 2024 13:08:13 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9A=A1=20Bot=20Upgrade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Video title, description & tags generation. - Automating video upload to youtube --- main.py | 18 +++++- utils/.config.template.toml | 7 ++- utils/background_audios.json | 5 ++ utils/subreddit.py | 7 ++- utils/youtube_uploader.py | 36 +++++++++++ video_creation/final_video.py | 11 ++++ video_data_generation/__init__.py | 0 video_data_generation/gemini.py | 76 +++++++++++++++++++++++ video_data_generation/image_generation.py | 66 ++++++++++++++++++++ 9 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 utils/youtube_uploader.py create mode 100644 video_data_generation/__init__.py create mode 100644 video_data_generation/gemini.py create mode 100644 video_data_generation/image_generation.py diff --git a/main.py b/main.py index 2af1daa..8f3c632 100755 --- a/main.py +++ b/main.py @@ -50,9 +50,12 @@ def main(POST_ID=None) -> None: global redditid, reddit_object reddit_object = get_subreddit_threads(POST_ID) redditid = id(reddit_object) + post_text = ' '.join(reddit_object['thread_post']) + length, number_of_comments = save_text_to_mp3(reddit_object) length = math.ceil(length) - # length, number_of_comments = 360, 43 + # length, number_of_comments = 42, 5 + get_screenshots_of_reddit_posts(reddit_object, number_of_comments) bg_config = { "video": get_background_config("video"), @@ -61,7 +64,15 @@ def main(POST_ID=None) -> None: 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) + video_path = make_final_video(number_of_comments, length, reddit_object, bg_config) + + video_data, thumbnail_text = get_video_data(post_text) + print("Video title:", video_data['title']) + print("Video description:", video_data['description']) + print("Video tags:", video_data['tags']) + # TODO: Generate thumbnail from text here + thumbnail = None + upload_video_to_youtube(video_path, video_data, thumbnail) def run_many(times) -> None: @@ -115,6 +126,9 @@ if __name__ == "__main__": ) config is False and sys.exit() + from video_data_generation.gemini import get_video_data + from utils.youtube_uploader import upload_video_to_youtube + if ( not settings.config["settings"]["tts"]["tiktok_sessionid"] or settings.config["settings"]["tts"]["tiktok_sessionid"] == "" diff --git a/utils/.config.template.toml b/utils/.config.template.toml index 6052fef..680426a 100644 --- a/utils/.config.template.toml +++ b/utils/.config.template.toml @@ -31,11 +31,11 @@ storymode_max_length = { optional = true, default = 1000, example = 1000, explan resolution_w = { optional = false, default = 1080, example = 1440, explantation = "Sets the width in pixels of the final video" } resolution_h = { optional = false, default = 1920, example = 2560, explantation = "Sets the height in pixels of the final video" } zoom = { optional = true, default = 1, example = 1.1, explanation = "Sets the browser zoom level. Useful if you want the text larger.", type = "float", nmin = 0.1, nmax = 2, oob_error = "The text is really difficult to read at a zoom level higher than 2" } -run_every = { optional = false, default = 24, example = 5, explanation = "How often should the bot create a video (in hours).", type = "int", nmin = 1, nmax = 48, oob_error = "Please choose a number between 1 and 24." } +run_every = { optional = false, default = 24, example = 5, explanation = "How often should the bot create a video (in hours).", type = "int", nmin = 4, nmax = 48, oob_error = "Please choose a number between 4 and 48." } [settings.background] background_video = { optional = true, default = "minecraft", example = "rocket-league", options = ["mudrunner", "granny-remake", ""], explanation = "Sets the background for the video based on game name" } -background_audio = { optional = true, default = "lofi", example = "chill-summer", options = ["lofi","lofi-2","chill-summer", "eerie",""], explanation = "Sets the background audio for the video" } +background_audio = { optional = true, default = "lofi", example = "chill-summer", options = ["eerie", "mysterious", "hybrid",""], explanation = "Sets the background audio for the video" } background_audio_volume = { optional = true, type = "float", nmin = 0, nmax = 1, default = 0.15, example = 0.05, explanation="Sets the volume of the background audio. If you don't want background audio, set it to 0.", oob_error = "The volume HAS to be between 0 and 1", input_error = "The volume HAS to be a float number between 0 and 1"} enable_extra_audio = { optional = true, type = "bool", default = false, example = false, explanation="Used if you want to render another video without background audio in a separate folder", input_error = "The value HAS to be true or false"} background_thumbnail = { optional = true, type = "bool", default = false, example = false, options = [true, false,], explanation = "Generate a thumbnail for the video (put a thumbnail.png file in the assets/backgrounds directory.)" } @@ -60,3 +60,6 @@ python_voice = { optional = false, default = "1", example = "1", explanation = " py_voice_num = { optional = false, default = "2", example = "2", explanation = "The number of system voices (2 are pre-installed in Windows)" } silence_duration = { optional = true, example = "0.1", explanation = "Time in seconds between TTS comments", default = 0.3, type = "float" } no_emojis = { optional = false, type = "bool", default = false, example = false, options = [true, false,], explanation = "Whether to remove emojis from the comments" } + +[settings.gemini] +gemini_api_key = { optional = false, example = "21f13f91f54d741e2ae27d2ab1b99d59", explanation = "Gemini API key" } \ No newline at end of file diff --git a/utils/background_audios.json b/utils/background_audios.json index 8cc352e..e714ee5 100644 --- a/utils/background_audios.json +++ b/utils/background_audios.json @@ -5,6 +5,11 @@ "eerie.mp3", "Royalty Free Music" ], + "mysterious":[ + "https://www.youtube.com/watch?v=lRwq7pfA4Gg", + "mysterious.mp3", + "Royalty Free Music" + ], "hybrid":[ "https://www.youtube.com/watch?v=QbqkR5VNaU8", "hybrid.mp3", diff --git a/utils/subreddit.py b/utils/subreddit.py index f8e60ed..e51cdd3 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -57,16 +57,19 @@ def get_subreddit_undone(submissions: list, subreddit, times_checked=0, similari else: # Check for the length of the post text if len(submission.selftext) > ( - settings.config["settings"]["storymode_max_length"] or 2000 + settings.config["settings"]["storymode_max_length"] ): print_substep( f"Post is too long ({len(submission.selftext)}), try with a different post. ({settings.config['settings']['storymode_max_length']} character limit)" ) continue - elif len(submission.selftext) < 30: + elif len(submission.selftext) < 400: continue if settings.config["settings"]["storymode"] and not submission.is_self: continue + if submission.upvote_ratio * 100 < 70 and not settings.config["reddit"]["thread"]["post_id"]: + print(f"Found a post with upvote ratio {submission.upvote_ratio*100}% which is less than 70%. Skipping...") + continue if similarity_scores is not None: return submission, similarity_scores[i].item() return submission diff --git a/utils/youtube_uploader.py b/utils/youtube_uploader.py new file mode 100644 index 0000000..dd9f07f --- /dev/null +++ b/utils/youtube_uploader.py @@ -0,0 +1,36 @@ +from simple_youtube_api.Channel import Channel +from simple_youtube_api.LocalVideo import LocalVideo + +from video_data_generation import gemini + + +# logging into the channel +channel = Channel() +channel.login("./utils/client_secret.json", "./utils/credentials.storage") + +def upload_video_to_youtube(video_path, data, thumbnail): + # setting up the video that is going to be uploaded + video = LocalVideo(file_path=video_path) + + # setting snippet + video.set_title(data['title']) + video.set_description(data["description"]) + tags = data["tags"].split(', ') + if len(tags) == 1: tags = tags[0].split(',') + video.set_tags(tags) + # video.set_category("gaming") + video.set_default_language("en-US") + + # setting status + video.set_embeddable(True) + video.set_license("youtube") + video.set_privacy_status("public") + video.set_public_stats_viewable(True) + + # setting thumbnail + if thumbnail is not None: video.set_thumbnail_path(thumbnail) + + # uploading video and printing the results + video = channel.upload_video(video) + print(video.id) + print(video) \ No newline at end of file diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 972aa70..2f284ed 100644 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -144,6 +144,16 @@ def make_final_video( reddit_obj (dict): The reddit object that contains the posts to read. background_config (Tuple[str, str, str, Any]): The background config to use. """ + # title = re.sub(r"[^\w\s-]", "", reddit_obj["thread_title"]) + # filename = f"{name_normalize(title)[:251]}" + # p = f'results/{settings.config["reddit"]["thread"]["subreddit"]}' + f"/{filename}" + # print(( + # p[:251] + ".mp4" + # )) + # return ( + # p[:251] + ".mp4" + # ) + # settings values W: Final[int] = int(settings.config["settings"]["resolution_w"]) H: Final[int] = int(settings.config["settings"]["resolution_h"]) @@ -444,3 +454,4 @@ def make_final_video( cleanups = cleanup(reddit_id) print_substep(f"Removed {cleanups} temporary files 🗑") print_step("Done! 🎉 The video is in the results folder 📁") + return path diff --git a/video_data_generation/__init__.py b/video_data_generation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/video_data_generation/gemini.py b/video_data_generation/gemini.py new file mode 100644 index 0000000..09cd44b --- /dev/null +++ b/video_data_generation/gemini.py @@ -0,0 +1,76 @@ +import json +import google.generativeai as genai +from utils import settings + + +genai.configure(api_key=settings.config["settings"]["gemini"]["gemini_api_key"]) + +prompt1 = """I make some youtube videos where I get subreddits about ghost stories and make a video of it. + I will send a post to you and you should generate a YouTube video title, tags and description for it. + Only respond with the following: + title::: "The video title" + description::: "The video description" + tags::: "The video tags" without hashtags and separated by commas + + Here is the post: + """ + +prompt2 = """Describe a thumbnail for a youtube video about ghosts and ghost stories. +Only respond with the thumbnail description. +The description should be clear for an AI image generator model to generate it. + +The video tags are: +""" + +model = genai.GenerativeModel('gemini-pro') + +def get_data(post): + response = model.generate_content(prompt1 + post) + + text = response.text.split('\n') + data = {i.split(':::')[0].strip(): i.split(':::')[1].strip() for i in text} + return data + +def get_thumbnail(post): + thumbnail_response = model.generate_content(prompt2+post) + return thumbnail_response.text + +def get_video_data(post): + data = None + thumbnail = None + print("Generating video title & description...") + for i in range(2): + if i != 0: print("Try:", i+1) + try: + if data is None: data = get_data(post) + if data and thumbnail is None: + thumbnail = get_thumbnail(data['tags']) + break + except Exception as e: + # print(e) + continue + return data, thumbnail + +if __name__ == '__main__': + post = """ + +This is my dads story. With all of the weirdest things that have happened, this one takes the cake. + +My dad doesn't really speak of many paranormal experiences. This one freaked him out enough though, that he told me. One night, he went to bed with my mom and their dogs. My dad falls asleep pretty fast but for some reason that night, he couldn't. + +Some background info: My parents at the time had two cats, Sweetpea and Baby Princess( my sister named her when she was 6) Their cats would sometimss go into their room when they were sleeping. My parents always slept with their door open(I could never, creeps me out). Their room is the first room when you would reach the top of the steps. + +Anyways, my dad was laying in bed tossing and turning. He flipped on his back and closed his eyes. When he was laying there, he felt a pressure going up his legs. He figured the cats were walking up his legs. He then realized the pressure was going up his legs and ended up to his chest. He couldn't move a muscle. That's when he started to choke. His eyes popped open and he looked over and there was a figure standing over him with his hands around my dads throat. + +According to my dad, the figure had a black WW2 Nazi uniform on with the black uniform hat that had a swastika on it. The choking lasted for some time until my dad told it to get the fuck off of him, then it disappeared. My dad was freaked out. He got up and checked inside all of our bedrooms and throughout the house but couldn't find anyone anywhere. Everyone was asleep. + +I asked my dad why he thought the Nazi was choking him. He said he wasn't sure why, maybe he was an old angry relative. My grandma grew up in Germany through the war and my grandmas dad was a Nazi. So to say the least, we had some relatives that were in the war. I'm not sure why this ghost attacked my dad, maybe because he was the strongest in the house? Who knows. + +My dad isn't one to make up stories like this, so I believe him. He's a very strong and brave man, and it takes a lot for him to get scared. He told me that this really got to him and that he was scared for us and this is why he got up and searched throughout the house. Usually, if paranormal things happen, he doesn't say much because he didn't want to scare us when we were kids. + +I know that some of you might think that he had sleep paralysis when this happened, but my dad did not suffer from this. He told me that this was the first and only time that he couldn't move like this. + +Thank you for reading! Let me know what you guys think :) +""" + for i in get_video_data(post): + print(i, end="\n\n") \ No newline at end of file diff --git a/video_data_generation/image_generation.py b/video_data_generation/image_generation.py new file mode 100644 index 0000000..ca5a327 --- /dev/null +++ b/video_data_generation/image_generation.py @@ -0,0 +1,66 @@ +import requests +from bs4 import BeautifulSoup + + +s = requests.session() + +def renew_connection(): + csrf_url = "https://image.plus/" + payload = {} + + csrf_headers = { + 'Host': 'image.plus', + 'Sec-Ch-Ua': '"Chromium";v="121", "Not A(Brand";v="99"', + 'Sec-Ch-Ua-Mobile': '?0', + 'Sec-Ch-Ua-Platform': '"Windows"', + 'Upgrade-Insecure-Requests': '1', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Sec-Fetch-Site': 'none', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-User': '?1', + 'Sec-Fetch-Dest': 'document', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en-US,en;q=0.9', + 'Priority': 'u=0, i', + 'Connection': 'close' + } + + s.cookies.clear() + response = s.request("GET", csrf_url, headers=csrf_headers) + print(response.status_code) + soup = BeautifulSoup(response.text, 'html.parser') + return soup.find("meta", {"name":"csrf-token"})['content'] + + +def generate_image(csrf_token, prompt=None): + url = "https://image.plus/images/generate" + + prompt = "A ghost flying with a person sitting scared on a couch." + payload = f"prompt={prompt.replace(' ', '+')}&negative_prompt=&model=1&style=cinematic&samples=1&size=1152x896" + headers = { + 'Host': 'image.plus', + 'Content-Length': '106', + 'Sec-Ch-Ua': '"Chromium";v="121", "Not A(Brand";v="99"', + 'Sec-Ch-Ua-Mobile': '?0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'Accept': 'application/json, text/javascript, */*; q=0.01', + 'X-Requested-With': 'XMLHttpRequest', + 'Sec-Ch-Ua-Platform': '"Windows"', + 'Origin': 'https://image.plus', + 'Sec-Fetch-Site': 'same-origin', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Dest': 'empty', + 'Referer': 'https://image.plus/', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en-US,en;q=0.9', + 'Priority': 'u=1, i', + 'X-Csrf-Token': csrf_token, + } + + response = s.request("POST", url, headers=headers, data=payload) + return response.json() + +image = generate_image(renew_connection()) +print(image) \ No newline at end of file