Respire fundo e trabalhe nesse problema passo a passo e quando enfim encontrar solução, mande o código completo:

"""
Algumas letras da legenda do vídeo ocorre um pequeno bug.

Se por exemplo tiver uma palavra que contenha um caractere maior, exemplo: QUEIJO como na letra Q tem uma pontinha para baixo, quando o vermelho vai passando nessa letra essa pontinha é "cortada", da mesma forma acontece por exemplo em caracteres, por exemplo a: `,` `;` `'` ``` e etc..

SCRIPT:

```python
import re
from datetime import timedelta
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import subprocess
import os
import multiprocessing

font_size = 85
image_size = (1280, 720)
font_color = (255, 0, 0)  # Vermelho
highlight_color = (255, 255, 255)  # Branco
upper_case = True
fps = 30

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

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))
    return subtitles

def create_gradient_word(word, font, width, height, start_color, end_color, progress):
    word_image = Image.new('RGBA', (int(width), height), (0, 0, 0, 0))
    draw = ImageDraw.Draw(word_image)
    
    text_width, text_height = font.getsize(word)
    
    x = 0
    y = height  # Use a altura total da imagem
    
    draw.text((x, y), word, font=font, fill=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=0.5))
    
    gradient = Image.new('RGBA', word_image.size, (0, 0, 0, 0))
    for x in range(word_image.width):
        ratio = max(0, min(1, (x / width - progress) / 0.1))
        r = int(start_color[0] + (end_color[0] - start_color[0]) * ratio)
        g = int(start_color[1] + (end_color[1] - start_color[1]) * ratio)
        b = int(start_color[2] + (end_color[2] - start_color[2]) * ratio)
        color = (r, g, b, 255)
        ImageDraw.Draw(gradient).line([(x, 0), (x, word_image.height)], fill=color)
    
    gradient.putalpha(mask)
    
    return gradient

def generate_frames_chunk(args):
    start_frame, end_frame, subtitles, output_dir, font_path, image_size, highlight_color, font_color, fps, upper_case = args

    font = ImageFont.truetype(font_path, font_size)
    space_width = font.getlength(' ')

    max_words_per_line = 4
    num_lines = 4
    line_spacing = 10
    max_width = image_size[0] - 40  # Margem de 20 pixels de cada lado

    words = [word.upper() if upper_case else word for t, word in subtitles]
    word_widths = [font.getlength(word) for word in words]

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

    total_duration = (subtitles[-1][0] + timedelta(seconds=2)).total_seconds()

    for frame_index in range(start_frame, end_frame):
        current_time = timedelta(seconds=frame_index / fps)
        image = background_image.copy()
        draw = ImageDraw.Draw(image)

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

        # Encontrar o início da frase atual
        start_index = current_word_index
        while start_index > 0 and not words[start_index - 1].endswith('.'):
            start_index -= 1

        # Encontrar o fim da frase atual
        end_index = start_index
        while end_index < len(words) and not words[end_index].endswith('.'):
            end_index += 1
        end_index = min(end_index + 1, len(words))

        # Verificar se é uma frase completa
        is_complete_sentence = words[start_index][0].isupper() and words[end_index - 1].endswith('.')

        if is_complete_sentence:
            sentence_words = words[start_index:end_index]
            sentence_widths = word_widths[start_index:end_index]
            
            # Quebrar a frase em linhas
            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) * 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 = start_index + sentence_words.index(word)
                    word_time = subtitles[word_index][0]
                    
                    if word_index + 1 < len(subtitles):
                        next_word_time = subtitles[word_index + 1][0]
                    else:
                        next_word_time = word_time + timedelta(seconds=2)

                    ascent, descent = font.getmetrics()
                    text_width = font.getlength(word)
                    y_centered = y + ascent

                    if current_time >= next_word_time:
                        draw.text((x, y_centered), word, font=font, fill=font_color, anchor="ls")
                    elif current_time >= word_time:
                        word_duration = (next_word_time - word_time).total_seconds()
                        progress = (current_time - word_time).total_seconds() / word_duration
                        progress = min(max(progress, 0.0), 1.0)
                        gradient_image = create_gradient_word(word, font, text_width, font_size, font_color, highlight_color, progress)
                        image.paste(gradient_image, (int(x), int(y_centered - font_size)), gradient_image)
                    else:
                        draw.text((x, y_centered), word, font=font, fill=highlight_color, anchor="ls")

                    x += text_width + space_width

                y += font_size + line_spacing
        else:
            # Usar a lógica original para palavras que não fazem parte de uma frase completa
            start_index = (current_word_index // (max_words_per_line * num_lines)) * (max_words_per_line * num_lines)
            total_height = num_lines * font_size + (num_lines - 1) * line_spacing
            y = (image_size[1] - total_height) // 2

            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 i, word in enumerate(line_words):
                    word_index = line_start + i
                    word_time = subtitles[word_index][0]
                    
                    if word_index + 1 < len(subtitles):
                        next_word_time = subtitles[word_index + 1][0]
                    else:
                        next_word_time = word_time + timedelta(seconds=2)

                    ascent, descent = font.getmetrics()
                    text_width = font.getlength(word)
                    y_centered = y + ascent

                    if current_time >= next_word_time:
                        draw.text((x, y_centered), word, font=font, fill=font_color, anchor="ls")
                    elif current_time >= word_time:
                        word_duration = (next_word_time - word_time).total_seconds()
                        progress = (current_time - word_time).total_seconds() / word_duration
                        progress = min(max(progress, 0.0), 1.0)
                        gradient_image = create_gradient_word(word, font, text_width, font_size, font_color, highlight_color, progress)
                        image.paste(gradient_image, (int(x), int(y_centered - font_size)), gradient_image)
                    else:
                        draw.text((x, y_centered), word, font=font, fill=highlight_color, anchor="ls")

                    x += text_width + space_width

                y += font_size + line_spacing

        frame_filename = os.path.join(output_dir, f"frame_{frame_index:04d}.jpg")
        image.save(frame_filename)

def generate_frames_parallel(subtitles, output_dir):
    font_path = os.path.join(os.path.dirname(__file__), "impact.ttf")
    font = ImageFont.truetype(font_path, font_size)
    space_width = font.getlength(' ')

    # Calculate total duration
    total_duration = (subtitles[-1][0] + timedelta(seconds=2)).total_seconds()
    num_frames = int(total_duration * fps) + 1

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

    args_list = [
        (
            i * chunk_size,
            min((i + 1) * chunk_size, num_frames),
            subtitles,
            output_dir,
            font_path,
            image_size,
            highlight_color,
            font_color,
            fps,
            upper_case
        )
        for i in range(num_processes)
    ]

    with multiprocessing.Pool() as pool:
        pool.map(generate_frames_chunk, args_list)

def create_video(frame_folder, output_video, framerate, audio_file):
    frame_pattern = os.path.join(frame_folder, 'frame_%04d.jpg')
    command = [
        'ffmpeg',
        '-y',
        '-framerate', str(framerate),
        '-i', frame_pattern,
        '-i', audio_file,
        '-c:v', 'h264_nvenc',
        '-preset', 'slow',
        '-crf', '23',
        '-c:a', 'aac',
        '-strict', 'experimental',
        '-shortest',
        output_video
    ]
    subprocess.run(command, check=True)

if __name__ == "__main__":
    subtitles = read_subtitles('legenda.srt')
    output_dir = 'frames'
    os.makedirs(output_dir, exist_ok=True)

    generate_frames_parallel(subtitles, output_dir)
    create_video(output_dir, 'output_video.mp4', fps, 'audio.wav')
```