Use outra abordagem mais rapida para criar os quadros da pasta frame

[Mande o código completo]

```python
import re
from datetime import timedelta
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance
import subprocess
import os
import multiprocessing
from functools import lru_cache
import shutil
import math
import random
from tqdm import tqdm
import time

image_size = (1280, 720)
font_color = "#FF0000"  # Vermelho
highlight_color = "#FFFF00"  # Amarelo
upper_case = False
fps = 30
fade_duration = 3  # Duração do fade em segundos

def hex_to_rgb(hex_color):
    return tuple(int(hex_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))

def parse_time(time_str):
    match = re.match(r'(\d+):(\d+):(\d+)', time_str)
    if match:
        minutes, seconds, milliseconds = map(int, match.groups())
        return timedelta(minutes=minutes, seconds=seconds, milliseconds=milliseconds)
    return timedelta(0)

def get_audio_duration(audio_file):
    command = [
        'ffprobe',
        '-v', 'error',
        '-show_entries', 'format=duration',
        '-of', 'default=noprint_wrappers=1:nokey=1',
        audio_file
    ]
    result = subprocess.run(command, capture_output=True, text=True)
    return float(result.stdout)

def read_subtitles(filename):
    subtitles = []
    with open(filename, 'r', encoding='utf-8') as file:
        for line in file:
            match = re.match(r'\[(\d+:\d+:\d+)\] (.+)', line.strip())
            if match:
                time_str, word = match.groups()
                subtitles.append((parse_time(time_str), word.upper() if upper_case else word))
    
    audio_duration = get_audio_duration('audio.wav')
    final_timestamp = timedelta(seconds=audio_duration)
    subtitles.append((final_timestamp, ""))
    
    return subtitles

@lru_cache(maxsize=1000)
def create_gradient_word(word, width, height, start_color, end_color, progress, font_size, stroke_progress):
    font = ImageFont.truetype(r"C:\Windows\pragmatic.ttf", font_size)
    ascent, descent = font.getmetrics()
    total_height = ascent + descent

    padding = 20
    word_image = Image.new('RGBA', (int(width) + padding, total_height + padding), (0, 0, 0, 0))
    draw = ImageDraw.Draw(word_image)
    
    x = padding // 2
    y = ascent

    stroke_width = 4
    stroke_opacity = int(255 * stroke_progress)
    stroke_fill = (255, 255, 255, stroke_opacity)
    
    draw.text((x, y), word, font=font, fill=(0, 0, 0, 0), stroke_width=stroke_width, stroke_fill=stroke_fill, anchor="ls")
    draw.text((x, y), word, font=font, fill=hex_to_rgb(end_color), anchor="ls")
    
    mask = Image.new('L', word_image.size, 0)
    mask_draw = ImageDraw.Draw(mask)
    mask_draw.text((x, y), word, font=font, fill=255, anchor="ls")
    mask = mask.filter(ImageFilter.GaussianBlur(radius=1.0))
    
    gradient = Image.new('RGBA', word_image.size, (0, 0, 0, 0))
    start_color_rgb = hex_to_rgb(start_color)
    end_color_rgb = hex_to_rgb(end_color)
    
    for x_coord in range(word_image.width):
        ratio = max(0, min(1, (x_coord / (width + padding) - progress) / 0.1))
        r = int(start_color_rgb[0] + (end_color_rgb[0] - start_color_rgb[0]) * ratio)
        g = int(start_color_rgb[1] + (end_color_rgb[1] - start_color_rgb[1]) * ratio)
        b = int(start_color_rgb[2] + (end_color_rgb[2] - start_color_rgb[2]) * ratio)
        color = (r, g, b, 255)
        ImageDraw.Draw(gradient).line([(x_coord, 0), (x_coord, word_image.height)], fill=color)
    
    gradient.putalpha(mask)
    
    result = Image.alpha_composite(word_image, gradient)
    
    return result

def create_star(size, color):
    star = Image.new('RGBA', (size, size), (0, 0, 0, 0))
    draw = ImageDraw.Draw(star)
    draw.ellipse([0, 0, size, size], fill=color)
    return star

def create_watermark(text, font_path, size, opacity=0.02, font_size=5):
    watermark = Image.new('RGBA', size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(watermark)
    
    font = ImageFont.truetype(font_path, font_size)
    
    bbox = draw.textbbox((0, 0), text, font=font)
    text_width = bbox[2] - bbox[0]
    text_height = bbox[3] - bbox[1]
    position = ((size[0] - text_width) // 2, 10)
    
    draw.text(position, text, font=font, fill=(255, 255, 255, int(255 * opacity)))
    
    return watermark

def add_stars(image, stars, frame_index):
    for i, (x, y, star, max_brightness) in enumerate(stars):
        brightness = max_brightness * abs(math.sin(frame_index * 0.1 + i * 0.5))
        enhancer = ImageEnhance.Brightness(star)
        bright_star = enhancer.enhance(brightness)
        image.paste(bright_star, (x, y), bright_star)

def add_watermark(image, frame_index, watermark1, watermark2, fps):
    watermark_interval = 34 * fps
    watermark1_duration = 30 * fps
    watermark2_duration = 4 * fps
    fade_duration_frames = int(fade_duration * fps)

    cycle_frame = frame_index % watermark_interval
    if cycle_frame < watermark1_duration:
        if cycle_frame < fade_duration_frames:
            opacity = cycle_frame / fade_duration_frames
        elif cycle_frame >= watermark1_duration - fade_duration_frames:
            opacity = (watermark1_duration - cycle_frame) / fade_duration_frames
        else:
            opacity = 1.0
        current_watermark = watermark1
    else:
        if cycle_frame < watermark1_duration + fade_duration_frames:
            opacity = (cycle_frame - watermark1_duration) / fade_duration_frames
        elif cycle_frame >= watermark_interval - fade_duration_frames:
            opacity = (watermark_interval - cycle_frame) / fade_duration_frames
        else:
            opacity = 1.0
        current_watermark = watermark2

    watermark_with_opacity = Image.new('RGBA', image.size, (0, 0, 0, 0))
    alpha = int(255 * opacity)
    watermark_mask = Image.new('L', image.size, alpha)
    watermark_with_opacity.paste(current_watermark, (0, 0), watermark_mask)
    return Image.alpha_composite(image.convert('RGBA'), watermark_with_opacity).convert('RGB')

def add_text(image, current_time, subtitles, words, word_widths, font, space_width, image_size, highlight_color, font_color):
    draw = ImageDraw.Draw(image)
    max_words_per_line = 4
    num_lines = 4
    line_spacing = 10
    max_width = image_size[0] - 40

    current_word_index = next((i for i, (word_time, _) in enumerate(subtitles) if word_time > current_time), len(subtitles) - 1)
    current_word_index = max(0, current_word_index - 1)

    start_index = current_word_index
    while start_index > 0 and not words[start_index - 1].endswith('.'):
        start_index -= 1

    end_index = next((i for i in range(start_index, len(words)) if words[i].endswith('.')), len(words))
    end_index = min(end_index + 1, len(words))

    is_complete_sentence = words[start_index][0].isupper() and words[end_index - 1].endswith('.')

    current_font_size = 130 if is_complete_sentence else 130

    word_pos = start_index

    if is_complete_sentence:
        sentence_words = words[start_index:end_index]
        sentence_widths = word_widths[start_index:end_index]
        
        lines = []
        current_line = []
        current_line_width = 0
        for word, width in zip(sentence_words, sentence_widths):
            if current_line_width + width + space_width > max_width and current_line:
                lines.append(current_line)
                current_line = []
                current_line_width = 0
            current_line.append(word)
            current_line_width += width + space_width
        if current_line:
            lines.append(current_line)
        
        total_height = len(lines) * current_font_size + (len(lines) - 1) * line_spacing
        y = (image_size[1] - total_height) // 2

        for line in lines:
            line_widths = [font.getlength(word) for word in line]
            total_width = sum(line_widths) + space_width * (len(line) - 1)
            start_x = (image_size[0] - total_width) // 2
            x = start_x

            for word in line:
                word_index = word_pos
                word_time = subtitles[word_index][0]
                
                next_word_time = subtitles[word_index + 1][0] if word_index + 1 < len(subtitles) else word_time + timedelta(seconds=2)

                ascent_word, descent_word = font.getmetrics()
                text_width = font.getlength(word)
                y_centered = y + ascent_word

                if current_time >= next_word_time:
                    color = font_color
                    progress = 1.0
                    stroke_progress = 1.0
                elif current_time >= word_time:
                    word_duration = (next_word_time - word_time).total_seconds()
                    progress = min(max((current_time - word_time).total_seconds() / word_duration, 0.0), 1.0)
                    color = font_color
                    stroke_progress = progress
                else:
                    color = highlight_color
                    progress = 1.0
                    stroke_progress = 0.0

                gradient_image = create_gradient_word(word, text_width, current_font_size, color, highlight_color, progress, current_font_size, stroke_progress)
                image.paste(gradient_image, (int(x - 5), int(y_centered - ascent_word - 5)), gradient_image)

                x += text_width + space_width
                word_pos += 1

            y += current_font_size + line_spacing
    else:
        start_index = (current_word_index // (max_words_per_line * num_lines)) * (max_words_per_line * num_lines)
        total_height = num_lines * current_font_size + (num_lines - 1) * line_spacing
        y = (image_size[1] - total_height) // 2

        word_pos = start_index

        for line in range(num_lines):
            line_start = start_index + line * max_words_per_line
            line_end = min(line_start + max_words_per_line, len(words))
            line_words = words[line_start:line_end]
            line_widths = word_widths[line_start:line_end]
            
            total_width = sum(line_widths) + space_width * (len(line_words) - 1)
            start_x = (image_size[0] - total_width) // 2
            x = start_x

            for word in line_words:
                word_index = word_pos
                word_time = subtitles[word_index][0]
                
                next_word_time = subtitles[word_index + 1][0] if word_index + 1 < len(subtitles) else word_time + timedelta(seconds=2)

                ascent_word, descent_word = font.getmetrics()
                text_width = font.getlength(word)
                y_centered = y + ascent_word

                if current_time >= next_word_time:
                    color = font_color
                    progress = 1.0
                    stroke_progress = 1.0
                elif current_time >= word_time:
                    word_duration = (next_word_time - word_time).total_seconds()
                    progress = min(max((current_time - word_time).total_seconds() / word_duration, 0.0), 1.0)
                    color = font_color
                    stroke_progress = progress
                else:
                    color = highlight_color
                    progress = 1.0
                    stroke_progress = 0.0

                gradient_image = create_gradient_word(word, text_width, current_font_size, color, highlight_color, progress, current_font_size, stroke_progress)
                image.paste(gradient_image, (int(x - 5), int(y_centered - ascent_word - 5)), gradient_image)

                x += text_width + space_width
                word_pos += 1

            y += current_font_size + line_spacing

def apply_fade(image, frame_index, num_frames, fade_duration, fps):
    if frame_index < fade_duration * fps:
        fade_opacity = frame_index / (fade_duration * fps)
        fade_overlay = Image.new('RGBA', image.size, (0, 0, 0, int(255 * (1 - fade_opacity))))
        return Image.alpha_composite(image.convert('RGBA'), fade_overlay).convert('RGB')
    elif frame_index > num_frames - fade_duration * fps:
        fade_opacity = (num_frames - frame_index) / (fade_duration * fps)
        fade_overlay = Image.new('RGBA', image.size, (0, 0, 0, int(255 * (1 - fade_opacity))))
        return Image.alpha_composite(image.convert('RGBA'), fade_overlay).convert('RGB')
    return image

def create_frame(frame_index, subtitles, words, word_widths, font, space_width, background_image, stars, watermark1, watermark2, image_size, highlight_color, font_color, fps, num_frames, fade_duration):
    current_time = timedelta(seconds=frame_index / fps)
    image = background_image.copy()

    add_stars(image, stars, frame_index)
    image = add_watermark(image, frame_index, watermark1, watermark2, fps)
    add_text(image, current_time, subtitles, words, word_widths, font, space_width, image_size, highlight_color, font_color)
    image = apply_fade(image, frame_index, num_frames, fade_duration, fps)

    return image

def generate_frames_chunk(args):
    start_frame, end_frame, subtitles, output_dir, font_path, image_size, highlight_color, font_color, fps, upper_case, stars, background_image, watermark1, watermark2, num_frames, fade_duration, progress_dict, process_id = args

    words = [word.upper() if upper_case else word for t, word in subtitles]
    font = ImageFont.truetype(font_path, 130)
    space_width = font.getlength(' ')
    word_widths = [font.getlength(word) for word in words]

    for frame_index in range(start_frame, end_frame):
        image = create_frame(frame_index, subtitles, words, word_widths, font, space_width, background_image, stars, watermark1, watermark2, image_size, highlight_color, font_color, fps, num_frames, fade_duration)
        
        frame_filename = os.path.join(output_dir, f"frame_{frame_index:04d}.jpg")
        image.save(frame_filename, quality=95)

        progress_dict[process_id] += 1

    return end_frame - start_frame

def create_stars(num_stars, image_size):
    stars = []
    for _ in range(num_stars):
        x = random.randint(0, image_size[0])
        y = random.randint(0, image_size[1])
        size = random.randint(1, 3)
        brightness = random.uniform(0.5, 1.0)
        star = create_star(size, (int(255 * brightness),) * 3 + (255,))
        stars.append((x, y, star, brightness))
    return stars

def create_args_list(num_processes, chunk_size, num_frames, subtitles, output_dir, font_path, image_size, highlight_color, font_color, fps, upper_case, stars, background_image, watermark1, watermark2, fade_duration, progress_dict):
    return [
        (
            i * chunk_size,
            min((i + 1) * chunk_size, num_frames),
            subtitles,
            output_dir,
            font_path,
            image_size,
            highlight_color,
            font_color,
            fps,
            upper_case,
            stars,
            background_image,
            watermark1,
            watermark2,
            num_frames,
            fade_duration,
            progress_dict,
            i
        )
        for i in range(num_processes)
    ]

def update_progress_bar(results, progress_dict, num_frames):
    with tqdm(total=num_frames, desc="Gerando frames", unit="frame") as pbar:
        while not results.ready():
            total_progress = sum(progress_dict.values())
            pbar.n = total_progress
            pbar.refresh()
            time.sleep(0.1)
        
        pbar.n = sum(progress_dict.values())
        pbar.refresh()

def generate_frames_parallel(subtitles, output_dir, background_image):
    font_path = r"C:\Windows\pragmatic.ttf"
    total_duration = (subtitles[-1][0] + timedelta(seconds=2)).total_seconds()
    num_frames = int(total_duration * fps) + 1

    stars = create_stars(100, image_size)
    watermark1 = create_watermark("@playdasegunda", font_path, image_size, opacity=0.1, font_size=18)
    watermark2 = create_watermark("SE ESTÀ GOSTANDO CURTA O VÍDEO E INSCREVA-SE EM NOSSO CANAL.", font_path, image_size, opacity=0.1, font_size=18)

    num_processes = multiprocessing.cpu_count()
    chunk_size = num_frames // num_processes + 1

    with multiprocessing.Manager() as manager:
        progress_dict = manager.dict({i: 0 for i in range(num_processes)})

        args_list = create_args_list(num_processes, chunk_size, num_frames, subtitles, output_dir, font_path, image_size, highlight_color, font_color, fps, upper_case, stars, background_image, watermark1, watermark2, fade_duration, progress_dict)

        with multiprocessing.Pool(processes=num_processes) as pool:
            results = pool.map_async(generate_frames_chunk, args_list)
            
            update_progress_bar(results, progress_dict, num_frames)

        total_frames_processed = sum(results.get())
        print(f"Total de frames processados: {total_frames_processed}")

def create_video(frame_folder, output_video, framerate, audio_file):
    frame_pattern = os.path.join(frame_folder, 'frame_%04d.jpg')
    duration = get_audio_duration(audio_file)
    fade_duration_seconds = fade_duration

    fade_out_start = duration - fade_duration_seconds

    command = [
        'ffmpeg',
        '-y',
        '-framerate', str(framerate),
        '-i', frame_pattern,
        '-i', audio_file,
        '-c:v', 'h264_nvenc',
        '-preset', 'medium',
        '-rc', 'vbr_hq',
        '-cq', '23',
        '-b:v', '10M',
        '-maxrate', '20M',
        '-bufsize', '20M',
        '-profile:v', 'high',
        '-c:a', 'aac',
        '-b:a', '320k',
        '-ar', '44100',
        '-vf', f"fade=t=in:st=0:d={fade_duration_seconds},fade=t=out:st={fade_out_start}:d={fade_duration_seconds}",
        '-shortest',
        output_video
    ]
    subprocess.run(command, check=True)

def main():
    subtitles = read_subtitles('legenda.srt')
    output_dir = 'frames'
    os.makedirs(output_dir, exist_ok=True)

    background_image_path = r"C:\Users\lucas\OneDrive\Documentos\capa.jpg"
    background_image = Image.open(background_image_path).convert("RGBA").resize(image_size)

    generate_frames_parallel(subtitles, output_dir, background_image)
    create_video(output_dir, 'output_video.mp4', fps, 'audio.wav')
    shutil.rmtree(output_dir)

if __name__ == "__main__":
    main()
```