diff --git a/fonts/Another-Danger-Slanted.otf b/fonts/Another-Danger-Slanted.otf new file mode 100644 index 0000000..577514a Binary files /dev/null and b/fonts/Another-Danger-Slanted.otf differ diff --git a/fonts/Another-Danger.otf b/fonts/Another-Danger.otf new file mode 100644 index 0000000..d99830a Binary files /dev/null and b/fonts/Another-Danger.otf differ diff --git a/fonts/DM Letter Studio - Personal or Demo Use License.pdf b/fonts/DM Letter Studio - Personal or Demo Use License.pdf new file mode 100644 index 0000000..d82a536 Binary files /dev/null and b/fonts/DM Letter Studio - Personal or Demo Use License.pdf differ diff --git a/fonts/Foul-Fiend.ttf b/fonts/Foul-Fiend.ttf new file mode 100644 index 0000000..de8d035 Binary files /dev/null and b/fonts/Foul-Fiend.ttf differ diff --git a/fonts/Melted-Monster.ttf b/fonts/Melted-Monster.ttf new file mode 100644 index 0000000..b17434a Binary files /dev/null and b/fonts/Melted-Monster.ttf differ diff --git a/fonts/Mystery-Lake.ttf b/fonts/Mystery-Lake.ttf new file mode 100644 index 0000000..4d0ac77 Binary files /dev/null and b/fonts/Mystery-Lake.ttf differ diff --git a/fonts/Sectar.otf b/fonts/Sectar.otf new file mode 100644 index 0000000..50e57b1 Binary files /dev/null and b/fonts/Sectar.otf differ diff --git a/fonts/Sectar_SP.otf b/fonts/Sectar_SP.otf new file mode 100644 index 0000000..e319154 Binary files /dev/null and b/fonts/Sectar_SP.otf differ diff --git a/fonts/TERMS LICENSE FONT.pdf b/fonts/TERMS LICENSE FONT.pdf new file mode 100644 index 0000000..a2427d7 Binary files /dev/null and b/fonts/TERMS LICENSE FONT.pdf differ diff --git a/main.py b/main.py index 8f3c632..2ffac3b 100755 --- a/main.py +++ b/main.py @@ -54,7 +54,7 @@ def main(POST_ID=None) -> None: length, number_of_comments = save_text_to_mp3(reddit_object) length = math.ceil(length) - # length, number_of_comments = 42, 5 + # length, number_of_comments = 120, 18 get_screenshots_of_reddit_posts(reddit_object, number_of_comments) bg_config = { @@ -70,8 +70,15 @@ def main(POST_ID=None) -> None: 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 + + thumbnail = generate_image(thumbnail_text, f"./assets/temp/{reddit_object['thread_id']}/thumbnail_image.png") + # thumbnail = "thumbnail.png" + thumbnail = add_text( + thumbnail_path=thumbnail, + text=video_data["thumbnail_text"], + save_path=f"./assets/temp/{reddit_object['thread_id']}/thumbnail.png" + ) + print("Thumbnail generated successfully at:", thumbnail) upload_video_to_youtube(video_path, video_data, thumbnail) @@ -127,6 +134,7 @@ if __name__ == "__main__": config is False and sys.exit() from video_data_generation.gemini import get_video_data + from video_data_generation.image_generation import generate_image, add_text from utils.youtube_uploader import upload_video_to_youtube if ( diff --git a/utils/.config.template.toml b/utils/.config.template.toml index 680426a..b8ae582 100644 --- a/utils/.config.template.toml +++ b/utils/.config.template.toml @@ -34,8 +34,8 @@ zoom = { optional = true, default = 1, example = 1.1, explanation = "Sets the br 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 = ["eerie", "mysterious", "hybrid",""], explanation = "Sets the background audio for the video" } +background_video = { optional = true, default = "mudrunner", example = "rocket-league", options = ["mudrunner", "granny-remake", ""], explanation = "Sets the background for the video based on game name" } +background_audio = { optional = true, default = "eerie", 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.)" } diff --git a/utils/imagenarator.py b/utils/imagenarator.py index 9b8eea5..24e2f9d 100644 --- a/utils/imagenarator.py +++ b/utils/imagenarator.py @@ -20,6 +20,8 @@ def draw_multiple_line_text( image_width, image_height = image.size lines = textwrap.wrap(text, width=wrap) y = (image_height / 2) - (((Fontperm[1] + (len(lines) * padding) / len(lines)) * len(lines)) / 2) + + c = 0 for line in lines: line_width, line_height = font.getsize(line) if transparent: @@ -49,7 +51,11 @@ def draw_multiple_line_text( font=font, fill=shadowcolor, ) - draw.text(((image_width - line_width) / 2, y), line, font=font, fill=text_color) + if isinstance(text_color, tuple) and len(text_color) == 3: + draw.text(((image_width - line_width) / 2, y), line, font=font, fill=text_color) + elif isinstance(text_color, list): + draw.text(((image_width - line_width) / 2, y), line, font=font, fill=text_color[c % len(text_color)]) + c += 1 y += line_height + padding diff --git a/utils/youtube_uploader.py b/utils/youtube_uploader.py index dd9f07f..7d18fdb 100644 --- a/utils/youtube_uploader.py +++ b/utils/youtube_uploader.py @@ -1,9 +1,13 @@ from simple_youtube_api.Channel import Channel from simple_youtube_api.LocalVideo import LocalVideo +import os from video_data_generation import gemini +if os.path.exists("./utils/credentials.storage"): + os.remove("./utils/credentials.storage") + # logging into the channel channel = Channel() channel.login("./utils/client_secret.json", "./utils/credentials.storage") diff --git a/video_data_generation/gemini.py b/video_data_generation/gemini.py index 09cd44b..f60f348 100644 --- a/video_data_generation/gemini.py +++ b/video_data_generation/gemini.py @@ -7,10 +7,12 @@ 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. + Also generate a short, relevant and catchy text to be written on the video thumbnail. Only respond with the following: title::: "The video title" description::: "The video description" tags::: "The video tags" without hashtags and separated by commas + thumbnail_text::: "The text to be wriiten on the video thumbnail" Here is the post: """ @@ -19,7 +21,7 @@ prompt2 = """Describe a thumbnail for a youtube video about ghosts and ghost sto Only respond with the thumbnail description. The description should be clear for an AI image generator model to generate it. -The video tags are: +The video description: """ model = genai.GenerativeModel('gemini-pro') @@ -27,19 +29,21 @@ model = genai.GenerativeModel('gemini-pro') def get_data(post): response = model.generate_content(prompt1 + post) + # print("Data:", response.prompt_feedback) 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) + thumbnail_response = model.generate_content(prompt2 + post) + # print('Thumbnail:', thumbnail_response.prompt_feedback) return thumbnail_response.text def get_video_data(post): data = None thumbnail = None print("Generating video title & description...") - for i in range(2): + for i in range(3): if i != 0: print("Try:", i+1) try: if data is None: data = get_data(post) @@ -47,7 +51,7 @@ def get_video_data(post): thumbnail = get_thumbnail(data['tags']) break except Exception as e: - # print(e) + print(e) continue return data, thumbnail diff --git a/video_data_generation/image_generation.py b/video_data_generation/image_generation.py index ca5a327..24de9cb 100644 --- a/video_data_generation/image_generation.py +++ b/video_data_generation/image_generation.py @@ -1,12 +1,15 @@ +import os import requests from bs4 import BeautifulSoup +from PIL import Image, ImageDraw, ImageFont + +from utils.imagenarator import draw_multiple_line_text s = requests.session() -def renew_connection(): +def get_csrf_token(): csrf_url = "https://image.plus/" - payload = {} csrf_headers = { 'Host': 'image.plus', @@ -26,18 +29,15 @@ def renew_connection(): '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): +def generate_image(prompt, save_path): url = "https://image.plus/images/generate" + csrf_token = get_csrf_token() - 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" + payload = f"prompt={prompt.replace(' ', '+')}&negative_prompt=&model=1&style=comic-book&samples=1&size=1152x896" headers = { 'Host': 'image.plus', 'Content-Length': '106', @@ -60,7 +60,43 @@ def generate_image(csrf_token, prompt=None): } response = s.request("POST", url, headers=headers, data=payload) - return response.json() + image_url = response.json()['images'][0]['src'] + image = s.get(image_url).content + with open(save_path, 'wb') as file: + file.write(image) + return save_path + +def add_text(thumbnail_path, text, save_path): + bg = "./assets/backgrounds/background.jpg" + if thumbnail_path is None: + thumbnail_path = "./assets/thumbnail_bg.png" + + img1 = Image.open(bg).convert("RGBA") + img2 = Image.open(thumbnail_path).resize((720, 720)).convert("RGBA") + img1.paste(img2, (0,0), mask=img2) + + # font = ImageFont.truetype(os.path.join("fonts", "Another-Danger.otf"), 50) + # font = ImageFont.truetype(os.path.join("fonts", "Foul-Fiend.ttf"), 30) + font = ImageFont.truetype(os.path.join("fonts", "Mystery-Lake.ttf"), 75) + size = (560, 720) + image = Image.new("RGBA", size, (0, 0, 0, 0)) + draw_multiple_line_text( + image, + text, + font, + [(255, 255, 255), (165, 42, 42)], + 20, + wrap=20, + transparent=True + ) + + img1.paste(image, (720,0), mask=image) + img1.show() + img1.save(save_path) + return save_path + -image = generate_image(renew_connection()) -print(image) \ No newline at end of file +if __name__ == '__main__': + prompt = "A hyper-realistic, scary image of a ghost flying in a room and a person sitting on a couch very scared looking at the ghost." + image_path = generate_image(prompt) + print(image_path) \ No newline at end of file