import random from PIL import Image, ImageDraw, ImageFont import math import wave import struct from pydub import AudioSegment from gtts import gTTS import os import moviepy.video.io.ImageSequenceClip import glob import json import numpy as np import nltk from nltk.corpus import words, wordnet nltk.download("words") nltk.download("wordnet") class JSON: def read(file): with open(f"{file}.json", "r", encoding="utf8") as file: data = json.load(file, strict=False) return data def dump(file, data): with open(f"{file}.json", "w", encoding="utf8") as file: json.dump(data, file, indent=4) config_data = JSON.read("config") # SETTINGS # w = config_data["WIDTH"] h = config_data["HEIGHT"] maxW = config_data["MAX_WIDTH"] maxH = config_data["MAX_HEIGHT"] minW = config_data["MIN_WIDTH"] minH = config_data["MIN_HEIGHT"] LENGTH = config_data["SLIDES"] AMOUNT = config_data["VIDEOS"] min_shapes = config_data["MIN_SHAPES"] max_shapes = config_data["MAX_SHAPES"] sample_rate = config_data["SOUND_QUALITY"] tts_enabled = config_data.get("TTS_ENABLED", True) tts_text = config_data.get("TTS_TEXT", "This is a default text for TTS.") audio_wave_type = config_data.get( "AUDIO_WAVE_TYPE", "sawtooth" ) # Options: sawtooth, sine, square slide_duration = config_data.get("SLIDE_DURATION", 1000) # Duration in milliseconds deform_level = config_data.get( "DEFORM_LEVEL", "none" ) # Options: none, low, medium, high color_mode = config_data.get("COLOR_MODE", "random") # Options: random, scheme, solid color_scheme = config_data.get( "COLOR_SCHEME", "default" ) # Placeholder for color schemes solid_color = config_data.get("SOLID_COLOR", "#FFFFFF") # Default solid color allowed_shapes = config_data.get( "ALLOWED_SHAPES", ["rectangle", "ellipse", "polygon", "triangle", "circle"] ) wave_vibe = config_data.get("WAVE_VIBE", "calm") # New config option for wave vibe top_left_text_enabled = config_data.get("TOP_LEFT_TEXT_ENABLED", True) top_left_text_mode = config_data.get( "TOP_LEFT_TEXT_MODE", "random" ) # Options: random, word words_topic = config_data.get( "WORDS_TOPIC", "random" ) # Options: random, introspective, action, nature, technology # Vibe presets for wave sound wave_vibes = { "calm": {"frequency": 200, "amplitude": 0.3, "modulation": 0.1}, "eerie": {"frequency": 600, "amplitude": 0.5, "modulation": 0.7}, "random": {}, # Randomized values will be generated "energetic": {"frequency": 800, "amplitude": 0.7, "modulation": 0.2}, "dreamy": {"frequency": 400, "amplitude": 0.4, "modulation": 0.5}, "chaotic": {"frequency": 1000, "amplitude": 1.0, "modulation": 1.0}, } color_schemes = { "pastel": [ (255, 182, 193), (176, 224, 230), (240, 230, 140), (221, 160, 221), (152, 251, 152), ], "dark_gritty": [ (47, 79, 79), (105, 105, 105), (0, 0, 0), (85, 107, 47), (139, 69, 19), ], "nature": [ (34, 139, 34), (107, 142, 35), (46, 139, 87), (32, 178, 170), (154, 205, 50), ], "vibrant": [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255)], "ocean": [ (0, 105, 148), (72, 209, 204), (70, 130, 180), (135, 206, 250), (176, 224, 230), ], } # Font scaling based on video size font_size = max(w, h) // 40 # Scales font size to make it smaller and more readable fnt = ImageFont.truetype("./FONT/sys.ttf", font_size) files = glob.glob("./IMG/*") for f in files: os.remove(f) print("REMOVED OLD FILES") def generate_string( length, charset="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ): result = "" for i in range(length): result += random.choice(charset) return result # Predefined word lists for specific topics introspective_words = [ "reflection", "thought", "solitude", "ponder", "meditation", "introspection", "awareness", "contemplation", "silence", "stillness", ] action_words = [ "run", "jump", "climb", "race", "fight", "explore", "build", "create", "overcome", "achieve", ] nature_words = [ "tree", "mountain", "river", "ocean", "flower", "forest", "animal", "sky", "valley", "meadow", ] technology_words = [ "computer", "robot", "network", "data", "algorithm", "innovation", "digital", "machine", "software", "hardware", ] def generate_word(theme="random"): if theme == "introspective": return random.choice(introspective_words) elif theme == "action": return random.choice(action_words) elif theme == "nature": return random.choice(nature_words) elif theme == "technology": return random.choice(technology_words) elif theme == "random": return random.choice(words.words()) else: return "unknown_theme" def append_wave(freq=None, duration_milliseconds=1000, volume=1.0): global audio vibe_params = wave_vibes.get(wave_vibe, wave_vibes["calm"]) if wave_vibe == "random": freq = random.uniform(100, 1000) if freq is None else freq amplitude = random.uniform(0.1, 1.0) modulation = random.uniform(0.1, 1.0) else: base_freq = vibe_params["frequency"] freq = ( random.uniform(base_freq * 0.7, base_freq * 1.3) if freq is None else freq ) amplitude = vibe_params["amplitude"] * random.uniform(0.7, 1.3) modulation = vibe_params["modulation"] * random.uniform(0.6, 1.4) num_samples = duration_milliseconds * (sample_rate / 1000.0) for x in range(int(num_samples)): wave_sample = amplitude * math.sin(2 * math.pi * freq * (x / sample_rate)) modulated_sample = wave_sample * ( 1 + modulation * math.sin(2 * math.pi * 0.5 * x / sample_rate) ) audio.append(volume * modulated_sample) return def save_wav(file_name): wav_file = wave.open(file_name, "w") nchannels = 1 sampwidth = 2 nframes = len(audio) comptype = "NONE" compname = "not compressed" wav_file.setparams((nchannels, sampwidth, sample_rate, nframes, comptype, compname)) for sample in audio: wav_file.writeframes(struct.pack("h", int(sample * 32767.0))) wav_file.close() return # Generate TTS audio using gTTS def generate_tts_audio(text, output_file): tts = gTTS(text=text, lang="en") tts.save(output_file) print(f"TTS audio saved to {output_file}") if tts_enabled: tts_audio_file = "./SOUND/tts_output.mp3" generate_tts_audio(tts_text, tts_audio_file) for xyz in range(AMOUNT): video_name = generate_string(6) # Generate a consistent video name for i in range(LENGTH): img = Image.new("RGB", (w, h)) img1 = ImageDraw.Draw(img) img1.rectangle([(0, 0), (w, h)], fill="white", outline="white") num_shapes = random.randint(min_shapes, max_shapes) for _ in range(num_shapes): shape_type = random.choice(allowed_shapes) x1, y1 = random.randint(0, w), random.randint(0, h) if deform_level == "none": x2, y2 = minW + (maxW - minW) // 2, minH + (maxH - minH) // 2 elif deform_level == "low": x2 = random.randint(minW, minW + (maxW - minW) // 4) y2 = random.randint(minH, minH + (maxH - minH) // 4) elif deform_level == "medium": x2 = random.randint(minW, minW + (maxW - minW) // 2) y2 = random.randint(minH, minH + (maxH - minH) // 2) elif deform_level == "high": x2 = random.randint(minW, maxW) y2 = random.randint(minH, maxH) if color_mode == "random": color = ( random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), ) elif color_mode == "scheme": scheme_colors = color_schemes.get(color_scheme, [(128, 128, 128)]) color = random.choice(scheme_colors) elif color_mode == "solid": color = tuple( int(solid_color.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4) ) if shape_type == "rectangle": img1.rectangle( [(x1, y1), (x1 + x2, y1 + y2)], fill=color, outline=color ) elif shape_type == "ellipse": img1.ellipse([(x1, y1), (x1 + x2, y1 + y2)], fill=color, outline=color) elif shape_type == "polygon": num_points = random.randint(3, 6) points = [ (random.randint(0, w), random.randint(0, h)) for _ in range(num_points) ] img1.polygon(points, fill=color, outline=color) elif shape_type == "triangle": points = [ (x1, y1), (x1 + random.randint(-x2, x2), y1 + y2), (x1 + x2, y1 + random.randint(-y2, y2)), ] img1.polygon(points, fill=color, outline=color) elif shape_type == "star": points = [] for j in range(5): outer_x = x1 + int(x2 * math.cos(j * 2 * math.pi / 5)) outer_y = y1 + int(y2 * math.sin(j * 2 * math.pi / 5)) points.append((outer_x, outer_y)) inner_x = x1 + int(x2 / 2 * math.cos((j + 0.5) * 2 * math.pi / 5)) inner_y = y1 + int(y2 / 2 * math.sin((j + 0.5) * 2 * math.pi / 5)) points.append((inner_x, inner_y)) img1.polygon(points, fill=color, outline=color) elif shape_type == "circle": radius = min(x2, y2) // 2 img1.ellipse( [(x1 - radius, y1 - radius), (x1 + radius, y1 + radius)], fill=color, outline=color, ) if top_left_text_enabled: if top_left_text_mode == "random": random_top_left_text = generate_string( 30, charset="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:',.<>?/", ) elif top_left_text_mode == "word": random_top_left_text = generate_word(words_topic) else: random_top_left_text = "" img1.text((10, 10), random_top_left_text, font=fnt, fill="black") # Add video name to bottom-left corner video_name_text = f"{video_name}.mp4" video_name_width = img1.textlength(video_name_text, font=fnt) video_name_height = font_size img1.text( (10, h - video_name_height - 10), video_name_text, font=fnt, fill="black" ) # Move slide info text to the top right corner slide_text = f"Slide {i}" text_width = img1.textlength(slide_text, font=fnt) text_height = font_size img1.text((w - text_width - 10, 10), slide_text, font=fnt, fill="black") img.save(f"./IMG/{str(i).zfill(4)}_{random.randint(1000, 9999)}.png") print("IMAGE GENERATION DONE") audio = [] for i in range(LENGTH): append_wave(None, duration_milliseconds=slide_duration, volume=0.25) save_wav("./SOUND/output.wav") print("WAV GENERATED") wav_audio = AudioSegment.from_file("./SOUND/output.wav", format="wav") if tts_enabled: tts_audio = AudioSegment.from_file(tts_audio_file, format="mp3") combined_audio = wav_audio.overlay(tts_audio, position=0) else: combined_audio = wav_audio combined_audio.export("./SOUND/output.m4a", format="adts") print("MP3 GENERATED") image_folder = "./IMG" fps = 1000 / slide_duration # Ensure fps is precise to handle timing discrepancies image_files = sorted( [f for f in glob.glob(f"{image_folder}/*.png")], key=lambda x: int(os.path.basename(x).split("_")[0]), ) # Ensure all frames have the same dimensions frames = [] first_frame = np.array(Image.open(image_files[0])) for idx, file in enumerate(image_files): frame = np.array(Image.open(file)) if frame.shape != first_frame.shape: print( f"Frame {idx} has inconsistent dimensions: {frame.shape} vs {first_frame.shape}" ) frame = np.resize(frame, first_frame.shape) # Resize if necessary frames.append(frame) print("Starting video compilation...") clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(frames, fps=fps) clip.write_videofile( f"./OUTPUT/{video_name}.mp4", audio="./SOUND/output.m4a", codec="libx264", audio_codec="aac", ) print("Video compilation finished successfully!")