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 from transformers import pipeline import torch NLLB_LANG_CODES = { 'pt': 'por_Latn', 'en': 'eng_Latn', 'es': 'spa_Latn', 'fr': 'fra_Latn', 'de': 'deu_Latn', 'it': 'ita_Latn', 'nl': 'nld_Latn', 'pl': 'pol_Latn', 'ru': 'rus_Cyrl', 'zh': 'zho_Hans' } def check_dependencies(): try: import torch import transformers import whisper return True except ImportError as e: messagebox.showerror("Erro de Dependência", "Por favor, instale todas as dependências necessárias:\n" "pip install torch transformers whisper") return False class VideoSubtitleApp: def __init__(self, root): self.root = root self.root.title("Extrator e Tradutor 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.translation_language = tk.StringVar(value='Português') self.status_var = tk.StringVar(value="Pronto") self.subtitles_list = [] # Dicionário de línguas para extração self.languages = { 'Português (Brasil)': 'pt', 'Português (Portugal)': 'pt', 'English': 'en', 'Español': 'es', 'Français': 'fr', 'Deutsch': 'de', 'Italiano': 'it' } # Dicionário para tradução self.translation_languages = { 'Português': 'pt', 'English': 'en', 'Español': 'es', 'Français': 'fr', 'Deutsch': 'de', 'Italiano': 'it', 'Nederlands': 'nl', 'Polski': 'pl', 'Русский': 'ru', '中文': 'zh' } # Criar interface self.create_widgets() # Inicializar modelos em threads separadas self.model_ready = False self.translator = None threading.Thread(target=self.initialize_models, daemon=True).start() def initialize_models(self): """Inicializa os modelos de Whisper e Tradução""" try: self.status_var.set("Carregando modelos...") # Inicializar Whisper self.model = whisper.load_model("base") # Usar modelo NLLB mais leve device = 0 if torch.cuda.is_available() else -1 self.translator = pipeline( "translation", model="facebook/nllb-200-distilled-600M", device=device ) self.model_ready = True self.status_var.set("Modelos carregados com sucesso") self.generate_button.config(state='normal') except Exception as e: self.status_var.set("Erro ao carregar modelos") messagebox.showerror("Erro", f"Erro ao carregar modelos:\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 Original:").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) ttk.Label(top_frame, text="🔄 Traduzir para:").pack(side=tk.LEFT, padx=5) translation_combo = ttk.Combobox(top_frame, values=list(self.translation_languages.keys()), textvariable=self.translation_language, state='readonly', width=20) translation_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.translate_button = ttk.Button(button_frame, text="🔄 Traduzir", command=self.translate_subtitles, state='disabled') self.translate_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 translate_subtitles(self): """Traduz as legendas para o idioma selecionado""" if not self.subtitles_list: messagebox.showwarning("Aviso", "Gere as legendas primeiro antes de traduzir.") return target_lang = self.translation_languages[self.translation_language.get()] if self.selected_language.get() == self.translation_language.get(): messagebox.showwarning("Aviso", "O idioma de origem e destino são os mesmos.") return self.progress.start() self.translate_button.config(state='disabled') threading.Thread(target=self._translate_process, daemon=True).start() def _translate_process(self): """Processo de tradução em background""" try: self.status_var.set("Traduzindo legendas...") # Obter texto atual current_text = self.subtitle_text.get('1.0', tk.END).strip() if not current_text: raise Exception("Nenhum texto para traduzir") # Obter idioma alvo target_lang = self.translation_languages[self.translation_language.get()] target_lang_code = NLLB_LANG_CODES.get(target_lang, 'por_Latn') # Português como fallback # Dividir em segmentos para preservar formato SRT segments = current_text.split('\n\n') translated_segments = [] total_segments = len(segments) for i, segment in enumerate(segments, 1): lines = segment.split('\n') if len(lines) >= 3: # Verifica se é um segmento válido # Traduz apenas o texto, mantém número e timestamp text_to_translate = '\n'.join(lines[2:]) try: translation = self.translator( text_to_translate, src_lang="eng_Latn", tgt_lang=target_lang_code, max_length=512 )[0]['translation_text'] print(f"Original: {text_to_translate}") print(f"Tradução: {translation}") except Exception as e: print(f"Erro ao traduzir segmento {i}: {str(e)}") translation = text_to_translate # mantém texto original em caso de erro # Reconstrói o segmento translated_segment = f"{lines[0]}\n{lines[1]}\n{translation}" translated_segments.append(translated_segment) # Atualiza status self.status_var.set(f"Traduzindo... {i}/{total_segments}") # Atualiza o texto na interface translated_text = '\n\n'.join(translated_segments) self.root.after(0, self._update_translation, translated_text) self.status_var.set("Tradução concluída!") except Exception as e: self.status_var.set("Erro na tradução") messagebox.showerror("Erro", f"Erro ao traduzir legendas: {str(e)}") finally: self.progress.stop() self.translate_button.config(state='normal') # [Resto dos métodos existentes permanece igual...] 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') self.translate_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') self.translate_button.config(state='normal') def save_subtitles(self): """Salva as legendas em arquivo""" try: # Determina sufixo baseado no idioma language_code = self.translation_languages.get(self.translation_language.get(), 'en') suffix = f"_{language_code}" if self.translation_language.get() != 'English' else "" # Gera nome do arquivo base_path = os.path.splitext(self.video_path.get())[0] output_path = f"{base_path}{suffix}.srt" # Salva arquivo 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__": if check_dependencies(): root = tk.Tk() app = VideoSubtitleApp(root) root.mainloop()