import tkinter as tk from tkinter import ttk, scrolledtext, filedialog, messagebox import threading import whisper import cv2 from datetime import timedelta import os import subprocess import numpy as np class VideoSubtitleApp: def __init__(self, root): self.root = root self.root.title("Extrator de Legendas") self.root.geometry("900x700") # Variáveis self.video_path = tk.StringVar() self.video_info = tk.StringVar() self.selected_language = tk.StringVar(value='English') self.status_var = tk.StringVar(value="Pronto") self.subtitles_list = [] # Dicionário de línguas self.languages = { 'Português (Brasil)': 'pt', 'Português (Portugal)': 'pt', 'English': 'en', 'Español': 'es', 'Français': 'fr', 'Deutsch': 'de', 'Italiano': 'it' } # Criar interface self.create_widgets() # Inicializar modelo em thread separada self.model_ready = False threading.Thread(target=self.initialize_whisper, daemon=True).start() def initialize_whisper(self): """Inicializa o modelo Whisper""" try: self.status_var.set("Carregando modelo...") # Usar modelo base para melhor equilíbrio self.model = whisper.load_model("base") self.model_ready = True self.status_var.set("Modelo carregado com sucesso") self.generate_button.config(state='normal') except Exception as e: self.status_var.set("Erro ao carregar modelo") messagebox.showerror("Erro", f"Erro ao carregar modelo Whisper:\n{str(e)}") def create_widgets(self): """Cria a interface gráfica""" # Frame principal main_frame = ttk.Frame(self.root, padding=(10, 10, 10, 10)) main_frame.grid(row=0, column=0, sticky="nsew") # Configurar grid self.root.grid_rowconfigure(0, weight=1) self.root.grid_columnconfigure(0, weight=1) main_frame.grid_columnconfigure(0, weight=1) # Frame superior top_frame = ttk.Frame(main_frame) top_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10)) # Botões e controles ttk.Button(top_frame, text="📂 Selecionar Vídeo", command=self.select_file).pack(side=tk.LEFT, padx=5) ttk.Label(top_frame, text="🌐 Idioma:").pack(side=tk.LEFT, padx=5) language_combo = ttk.Combobox(top_frame, values=list(self.languages.keys()), textvariable=self.selected_language, state='readonly', width=20) language_combo.pack(side=tk.LEFT, padx=5) # Caminho do arquivo path_frame = ttk.LabelFrame(main_frame, text="Arquivo Selecionado") path_frame.grid(row=1, column=0, sticky="ew", pady=(0, 10)) ttk.Label(path_frame, textvariable=self.video_path, wraplength=800).grid(row=0, column=0, padx=5, pady=5) # Informações do vídeo info_frame = ttk.LabelFrame(main_frame, text="Informações do Vídeo") info_frame.grid(row=2, column=0, sticky="ew", pady=(0, 10)) ttk.Label(info_frame, textvariable=self.video_info).grid(row=0, column=0, padx=5, pady=5) # Botões de ação button_frame = ttk.Frame(main_frame) button_frame.grid(row=3, column=0, pady=(0, 10)) self.generate_button = ttk.Button(button_frame, text="🎬 Gerar Legendas", command=self.generate_subtitles, state='disabled') self.generate_button.pack(side=tk.LEFT, padx=5) self.save_button = ttk.Button(button_frame, text="💾 Salvar", command=self.save_subtitles, state='disabled') self.save_button.pack(side=tk.LEFT, padx=5) # Barra de progresso self.progress = ttk.Progressbar(main_frame, mode='indeterminate') self.progress.grid(row=4, column=0, sticky="ew", pady=(0, 10)) # Editor de legendas editor_frame = ttk.LabelFrame(main_frame, text="Editor de Legendas") editor_frame.grid(row=5, column=0, sticky="nsew", pady=(0, 10)) editor_frame.grid_columnconfigure(0, weight=1) editor_frame.grid_rowconfigure(0, weight=1) self.subtitle_text = scrolledtext.ScrolledText( editor_frame, wrap=tk.WORD, font=('Consolas', 10) ) self.subtitle_text.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) # Barra de status status_frame = ttk.Frame(main_frame) status_frame.grid(row=6, column=0, sticky="ew") ttk.Label(status_frame, textvariable=self.status_var, relief=tk.SUNKEN).grid( row=0, column=0, sticky="ew") status_frame.grid_columnconfigure(0, weight=1) def select_file(self): """Seleciona arquivo de vídeo""" filename = filedialog.askopenfilename( title="Selecionar Vídeo", filetypes=[ ("Arquivos de Vídeo", "*.mp4 *.mkv *.avi"), ("Todos os Arquivos", "*.*") ] ) if filename: self.video_path.set(filename) self.load_video_info(filename) def load_video_info(self, filename): """Carrega informações do vídeo""" try: cap = cv2.VideoCapture(filename) fps = cap.get(cv2.CAP_PROP_FPS) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) duration = frame_count / fps width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) info = f"Duração: {str(timedelta(seconds=int(duration)))}\n" info += f"Resolução: {width}x{height}\n" info += f"FPS: {fps:.2f}\n" info += f"Formato: {os.path.splitext(filename)[1]}" self.video_info.set(info) cap.release() except Exception as e: messagebox.showerror("Erro", f"Erro ao carregar vídeo: {str(e)}") def extract_audio(self, video_path, audio_path): """Extrai o áudio do vídeo""" try: command = [ 'ffmpeg', '-i', video_path, '-vn', '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', '-y', audio_path ] process = subprocess.run( command, capture_output=True, text=True ) return os.path.exists(audio_path) and os.path.getsize(audio_path) > 0 except Exception as e: print(f"Erro na extração de áudio: {str(e)}") return False def process_audio_with_whisper(self, audio_path, language_code): """Processa o áudio usando Whisper""" try: # Configurar opções do Whisper options = { "language": language_code, "task": "transcribe", "verbose": False } # Realizar transcrição result = self.model.transcribe(audio_path, **options) # Processar segmentos segments = [] for i, segment in enumerate(result["segments"], 1): start_time = segment["start"] end_time = segment["end"] text = segment["text"].strip() if text: segment_str = f"{i}\n" segment_str += f"{self.format_timestamp(start_time)} --> {self.format_timestamp(end_time)}\n" segment_str += f"{text}\n\n" segments.append(segment_str) return segments except Exception as e: print(f"Erro detalhado no processamento do áudio: {str(e)}") raise def format_timestamp(self, seconds): """Converte segundos para formato SRT""" hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) millisecs = int((seconds * 1000) % 1000) return f"{hours:02d}:{minutes:02d}:{secs:02d},{millisecs:03d}" def generate_subtitles(self): """Inicia processo de geração de legendas""" if not self.video_path.get(): messagebox.showwarning("Aviso", "Selecione um vídeo primeiro.") return if not self.model_ready: messagebox.showwarning("Aviso", "Aguarde o modelo ser carregado.") return self.progress.start() self.generate_button.config(state='disabled') self.save_button.config(state='disabled') threading.Thread(target=self.process_video, daemon=True).start() def process_video(self): """Processa o vídeo e gera legendas""" audio_path = "temp_audio.wav" try: self.status_var.set("Extraindo áudio...") if not self.extract_audio(self.video_path.get(), audio_path): raise Exception("Falha na extração do áudio") self.status_var.set("Processando áudio...") language = self.languages[self.selected_language.get()] self.subtitles_list = self.process_audio_with_whisper(audio_path, language) if not self.subtitles_list: raise Exception("Nenhuma legenda gerada") self.status_var.set("Legendas geradas com sucesso!") self.root.after(0, self.update_subtitle_text) except Exception as e: self.status_var.set("Erro no processamento") messagebox.showerror("Erro", str(e)) finally: self.progress.stop() self.generate_button.config(state='normal') try: if os.path.exists(audio_path): os.remove(audio_path) except: pass def update_subtitle_text(self): """Atualiza o texto das legendas na interface""" self.subtitle_text.delete('1.0', tk.END) self.subtitle_text.insert('1.0', ''.join(self.subtitles_list)) self.save_button.config(state='normal') def save_subtitles(self): """Salva as legendas em arquivo""" try: output_path = os.path.splitext(self.video_path.get())[0] + ".srt" with open(output_path, 'w', encoding='utf-8') as f: f.write(self.subtitle_text.get('1.0', tk.END)) messagebox.showinfo("Sucesso", f"Legendas salvas em:\n{output_path}") except Exception as e: messagebox.showerror("Erro", f"Erro ao salvar legendas: {str(e)}") if __name__ == "__main__": root = tk.Tk() app = VideoSubtitleApp(root) root.mainloop()