USER
Sabe a ultima palavra? então, agora está funcionando, mas o problema é que nessa ultima palavra não está tendo a sincronização correta, ela agora passa normalmente (que era o que eu queria) mas passa mais rápido, ou seja, não está considerando o tempo do arquivo srt.
Não sei se o problema é no cálculo do progresso para a última palavra. Ou se é na forma como o progresso está sendo aplicado à renderização da palavra.
Pense passo a passo e mande o código completo e corrigido.
```python
import re
from datetime import datetime, timedelta
from PIL import Image, ImageDraw, ImageFont, ImageColor
import subprocess
import os
import multiprocessing
import math
import numpy as np
font_size = 80
image_size = (1280, 720)
background_color = (0, 0, 0)
font_color = (255, 0, 0) # Red
highlight_color = (255, 255, 255) # White
upper_case = True
fps = 20
words_per_line = 25
word_spacing = 22
outline_thickness = 1 # Outline thickness
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 = []
current_phrase = []
word_count = 0
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()
is_end_of_sentence = word[0].isupper() and word.endswith('.')
if (word[0].isupper() and word_count > 0 and not is_end_of_sentence) or (word_count > 0 and current_phrase[-1][1].endswith('.')):
subtitles.append(current_phrase)
current_phrase = []
word_count = 0
current_phrase.append((parse_time(time_str), word))
word_count += 1
if word_count == words_per_line and not is_end_of_sentence:
subtitles.append(current_phrase)
current_phrase = []
word_count = 0
if current_phrase:
subtitles.append(current_phrase)
return subtitles
def generate_frames_chunk(args):
start_frame, end_frame, subtitles, output_dir, font, image_size, background_color, highlight_color, font_color, fps, upper_case = args
font_path = os.path.join(os.path.dirname(__file__), "impact.ttf")
font = ImageFont.truetype(font_path, font_size)
background_image = Image.open(r"C:\Users\lucas\OneDrive\Documentos\capa.jpg").convert("RGB")
background_image = background_image.resize(image_size)
offsets = [(-outline_thickness, -outline_thickness),
(-outline_thickness, outline_thickness),
(outline_thickness, -outline_thickness),
(outline_thickness, outline_thickness)]
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_phrase = next(
(phrase for phrase in subtitles if phrase[0][0] <= current_time <= phrase[-1][0] + timedelta(milliseconds=500)),
None
)
if current_phrase:
phrase_start_time = current_phrase[0][0]
phrase_end_time = current_phrase[-1][0] + timedelta(milliseconds=500)
phrase_duration = (phrase_end_time - phrase_start_time).total_seconds()
current_word_index = next(
(i for i, (time, _) in enumerate(current_phrase) if time > current_time),
len(current_phrase)
) - 1
current_word_start_time = current_phrase[current_word_index][0]
if current_word_index + 1 < len(current_phrase):
next_word_start_time = current_phrase[current_word_index + 1][0]
else:
next_word_start_time = current_word_start_time + timedelta(milliseconds=500)
word_duration = max(0.001, (next_word_start_time - current_word_start_time).total_seconds())
word_progress = min(1, max(0, (current_time - current_word_start_time).total_seconds() / word_duration))
full_text = ' '.join(word for _, word in current_phrase)
if upper_case:
full_text = full_text.upper()
lines = wrap_text(full_text, font, image_size[0] * 0.9)
y = (image_size[1] - len(lines) * font_size) // 2
current_word_count = 0
for line in lines:
x = (image_size[0] - font.getlength(line)) // 2
words = line.split()
for word_index, word in enumerate(words):
if current_word_count < current_word_index:
# Previous words: fully red
color = font_color
draw_word(draw, x, y, word, font, color, offsets)
elif current_word_count == current_word_index:
# Current word: apply smooth gradient
word_width = font.getlength(word)
gradient_image = create_gradient_word(word, font, word_width, font_size, font_color, highlight_color, word_progress)
image.paste(gradient_image, (int(x), int(y)), gradient_image)
else:
# Next words: fully white
color = highlight_color
draw_word(draw, x, y, word, font, color, offsets)
x += font.getlength(word + ' ')
current_word_count += 1
y += font_size
frame_filename = os.path.join(output_dir, f"frame_{frame_index:04d}.jpg")
image.save(frame_filename)
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))
word_draw = ImageDraw.Draw(word_image)
for i in range(int(width)):
char_progress = i / width
if char_progress < progress:
color = start_color
else:
color = end_color
word_draw.line([(i, 0), (i, height)], fill=color)
# Apply text mask
mask = Image.new('L', (int(width), height), 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.text((0, 0), word, font=font, fill=255)
word_image.putalpha(mask)
return word_image
def draw_word(draw, x, y, word, font, color, offsets):
for offset_x, offset_y in offsets:
draw.text((x + offset_x, y + offset_y), word, font=font, fill=(0, 0, 0))
draw.text((x, y), word, font=font, fill=color)
def wrap_text(text, font, max_width):
words = text.split()
lines = []
current_line = []
current_width = 0
for word in words:
word_width = font.getlength(word)
if current_width + word_width <= max_width:
current_line.append(word)
current_width += word_width + word_spacing
else:
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
current_width = word_width + word_spacing
if current_line:
lines.append(' '.join(current_line))
return lines
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)
total_duration = subtitles[-1][-1][0].total_seconds() + 0.5 # Added extra time for the last word
num_frames = int(total_duration * fps) + 1
num_processes = multiprocessing.cpu_count()
chunk_size = num_frames // num_processes + 1
with multiprocessing.Pool() as pool:
args = [
(i * chunk_size, min((i + 1) * chunk_size, num_frames), subtitles, output_dir, font, image_size, background_color, highlight_color, font_color, fps, upper_case)
for i in range(num_processes)
]
pool.map(generate_frames_chunk, args)
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')
```ASSISTANT
Vi que o problema está na forma como o tempo da última palavra está sendo calculado. Quando o código chega na última palavra de uma frase ou do arquivo, ele está definindo um tempo fixo de 500ms para a duração dessa palavra, o que não considera o tempo real definido no arquivo de legenda.
Para corrigir isso, precisamos ajustar a forma como o `next_word_start_time` é calculado para a última palavra. Em vez de adicionar 500ms, devemos procurar o tempo de início da próxima palavra geral (mesmo que em outra frase) ou, se não houver mais palavras, usar a duração total do vídeo.
Aqui está o código corrigido:
```python
import re
from datetime import datetime, timedelta
from PIL import Image, ImageDraw, ImageFont, ImageColor
import subprocess
import os
import multiprocessing
import math
import numpy as np
font_size = 80
image_size = (1280, 720)
background_color = (0, 0, 0)
font_color = (255, 0, 0) # Red
highlight_color = (255, 255, 255) # White
upper_case = True
fps = 20
words_per_line = 25
word_spacing = 22
outline_thickness = 1 # Outline thickness
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 = []
current_phrase = []
word_count = 0
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()
is_end_of_sentence = word[0].isupper() and word.endswith('.')
if (word[0].isupper() and word_count > 0 and not is_end_of_sentence) or (word_count > 0 and current_phrase[-1][1].endswith('.')):
subtitles.append(current_phrase)
current_phrase = []
word_count = 0
current_phrase.append((parse_time(time_str), word))
word_count += 1
if word_count == words_per_line and not is_end_of_sentence:
subtitles.append(current_phrase)
current_phrase = []
word_count = 0
if current_phrase:
subtitles.append(current_phrase)
return subtitles
def generate_frames_chunk(args):
start_frame, end_frame, subtitles, output_dir, font, image_size, background_color, highlight_color, font_color, fps, upper_case, total_duration = args
font_path = os.path.join(os.path.dirname(__file__), "impact.ttf")
font = ImageFont.truetype(font_path, font_size)
background_image = Image.open(r"C:\Users\lucas\OneDrive\Documentos\capa.jpg").convert("RGB")
background_image = background_image.resize(image_size)
offsets = [(-outline_thickness, -outline_thickness),
(-outline_thickness, outline_thickness),
(outline_thickness, -outline_thickness),
(outline_thickness, outline_thickness)]
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_phrase = next(
(phrase for phrase in subtitles if phrase[0][0] <= current_time <= phrase[-1][0] + timedelta(milliseconds=500)),
None
)
if current_phrase:
phrase_start_time = current_phrase[0][0]
phrase_end_time = current_phrase[-1][0] + timedelta(milliseconds=500)
phrase_duration = (phrase_end_time - phrase_start_time).total_seconds()
current_word_index = next(
(i for i, (time, _) in enumerate(current_phrase) if time > current_time),
len(current_phrase)
) - 1
if current_word_index < 0:
current_word_index = 0 # Ajuste para evitar índices negativos
current_word_start_time = current_phrase[current_word_index][0]
# Calcular o tempo de início da próxima palavra
if current_word_index + 1 < len(current_phrase):
next_word_start_time = current_phrase[current_word_index + 1][0]
else:
# Procurar na próxima frase
current_phrase_index = subtitles.index(current_phrase)
if current_phrase_index + 1 < len(subtitles):
next_phrase = subtitles[current_phrase_index + 1]
next_word_start_time = next_phrase[0][0]
else:
# Última palavra, usar a duração total
next_word_start_time = timedelta(seconds=total_duration)
word_duration = max(0.001, (next_word_start_time - current_word_start_time).total_seconds())
word_progress = min(1, max(0, (current_time - current_word_start_time).total_seconds() / word_duration))
full_text = ' '.join(word for _, word in current_phrase)
if upper_case:
full_text = full_text.upper()
lines = wrap_text(full_text, font, image_size[0] * 0.9)
y = (image_size[1] - len(lines) * font_size) // 2
current_word_count = 0
for line in lines:
x = (image_size[0] - font.getlength(line)) // 2
words = line.split()
for word_index, word in enumerate(words):
if current_word_count < current_word_index:
# Palavras anteriores: totalmente vermelhas
color = font_color
draw_word(draw, x, y, word, font, color, offsets)
elif current_word_count == current_word_index:
# Palavra atual: aplicar gradiente
word_width = font.getlength(word)
gradient_image = create_gradient_word(word, font, word_width, font_size, font_color, highlight_color, word_progress)
image.paste(gradient_image, (int(x), int(y)), gradient_image)
else:
# Próximas palavras: totalmente brancas
color = highlight_color
draw_word(draw, x, y, word, font, color, offsets)
x += font.getlength(word + ' ')
current_word_count += 1
y += font_size
frame_filename = os.path.join(output_dir, f"frame_{frame_index:04d}.jpg")
image.save(frame_filename)
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))
word_draw = ImageDraw.Draw(word_image)
for i in range(int(width)):
char_progress = i / width
if char_progress < progress:
color = start_color
else:
color = end_color
word_draw.line([(i, 0), (i, height)], fill=color)
# Aplicar máscara de texto
mask = Image.new('L', (int(width), height), 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.text((0, 0), word, font=font, fill=255)
word_image.putalpha(mask)
return word_image
def draw_word(draw, x, y, word, font, color, offsets):
for offset_x, offset_y in offsets:
draw.text((x + offset_x, y + offset_y), word, font=font, fill=(0, 0, 0))
draw.text((x, y), word, font=font, fill=color)
def wrap_text(text, font, max_width):
words = text.split()
lines = []
current_line = []
current_width = 0
for word in words:
word_width = font.getlength(word)
if current_width + word_width <= max_width:
current_line.append(word)
current_width += word_width + word_spacing
else:
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
current_width = word_width + word_spacing
if current_line:
lines.append(' '.join(current_line))
return lines
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)
total_duration = subtitles[-1][-1][0].total_seconds() + 0.5 # Tempo total do vídeo
num_frames = int(total_duration * fps) + 1
num_processes = multiprocessing.cpu_count()
chunk_size = num_frames // num_processes + 1
with multiprocessing.Pool() as pool:
args = [
(i * chunk_size, min((i + 1) * chunk_size, num_frames), subtitles, output_dir, font, image_size, background_color, highlight_color, font_color, fps, upper_case, total_duration)
for i in range(num_processes)
]
pool.map(generate_frames_chunk, args)
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')
```
**Explicação das alterações:**
- **Cálculo do `next_word_start_time` para a última palavra:** Agora, se a palavra atual for a última da frase, o código procura a próxima palavra na próxima frase. Se não houver próxima frase (ou seja, estamos na última palavra do arquivo), ele define o `next_word_start_time` como a duração total do vídeo. Isso garante que a última palavra seja exibida pelo tempo correto conforme definido na legenda.
- **Passagem de `total_duration` para a função `generate_frames_chunk`:** Isso permite que a função tenha acesso à duração total do vídeo para calcular corretamente o tempo da última palavra.
Com essas alterações, o tempo da última palavra será sincronizado corretamente, respeitando o tempo definido no arquivo de legenda.