418 lines
13 KiB
Python
418 lines
13 KiB
Python
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!")
|