Tesseract Translator
import subprocess
import sys
import os
import platform
import tkinter as tk
from tkinter import messagebox
from pathlib import Path
# Check if we're running as compiled exe
IS_COMPILED = getattr(sys, 'frozen', False)
def create_installer_batch():
"""
Create a batch file to install all dependencies
"""
batch_content = """@echo off
echo ============================================================
echo SCREEN TRANSLATOR - DEPENDENCY INSTALLER
echo ============================================================
echo.
echo This script will install the required dependencies.
echo Please wait...
echo.
echo [1/3] Installing Python packages...
python -m pip install --upgrade pip
python -m pip install Pillow pystray keyboard pytesseract requests deep-translator
if %errorlevel% neq 0 (
echo.
echo ERROR: Failed to install Python packages.
echo Please make sure Python is installed and added to PATH.
echo.
pause
exit /b 1
)
echo.
echo [2/3] Checking Tesseract OCR...
rem Check if Tesseract is already installed
where tesseract >nul 2>&1
if %errorlevel% equ 0 (
echo Tesseract OCR is already installed.
goto :done
)
rem Check common installation paths
if exist "C:\\Program Files\\Tesseract-OCR\\tesseract.exe" (
echo Tesseract OCR found at "C:\\Program Files\\Tesseract-OCR\\"
goto :done
)
if exist "C:\\Program Files (x86)\\Tesseract-OCR\\tesseract.exe" (
echo Tesseract OCR found at "C:\\Program Files (x86)\\Tesseract-OCR\\"
goto :done
)
echo Tesseract OCR not found. Downloading installer...
rem Download Tesseract installer
powershell -Command "& {[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri 'https://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr-w64-setup-5.3.3.20231005.exe' -OutFile '%TEMP%\\tesseract_installer.exe'}"
if %errorlevel% neq 0 (
echo.
echo ERROR: Failed to download Tesseract installer.
echo Please check your internet connection.
echo.
pause
exit /b 1
)
echo.
echo [3/3] Installing Tesseract OCR...
echo.
echo IMPORTANT: Please follow the installation wizard:
echo - Click "Next" through the installer
echo - Keep the default installation path
echo - Make sure "English" language is selected
echo - Click "Install" and wait for completion
echo.
echo The installer will open in a moment...
timeout /t 3 >nul
start /wait "" "%TEMP%\\tesseract_installer.exe"
rem Clean up installer
del "%TEMP%\\tesseract_installer.exe" 2>nul
:done
echo.
echo ============================================================
echo INSTALLATION COMPLETE!
echo ============================================================
echo.
echo All dependencies have been installed successfully.
echo Please close this window and restart the Screen Translator.
echo.
pause
exit /b 0
"""
batch_file = "install_dependencies.bat"
try:
with open(batch_file, 'w') as f:
f.write(batch_content)
print(f"[SETUP] Created installer batch file: {batch_file}")
return batch_file
except Exception as e:
print(f"[ERROR] Failed to create batch file: {e}")
return None
def check_dependencies():
"""
Check if all required dependencies are installed
"""
print("[SETUP] Checking dependencies...")
missing_deps = []
# Check Python packages using importlib
import importlib.util
packages_to_check = {
'PIL': 'Pillow',
'pystray': 'pystray',
'keyboard': 'keyboard',
'pytesseract': 'pytesseract',
'requests': 'requests',
'deep_translator': 'deep_translator'
}
for import_name, display_name in packages_to_check.items():
try:
spec = importlib.util.find_spec(import_name)
if spec is not None:
print(f"[SETUP] ✓ {display_name} found")
else:
print(f"[SETUP] ✗ {display_name} missing")
missing_deps.append(display_name)
except (ImportError, ModuleNotFoundError, ValueError):
print(f"[SETUP] ✗ {display_name} missing")
missing_deps.append(display_name)
# Check Tesseract
tesseract_found = False
tesseract_paths = [
r"C:\Program Files\Tesseract-OCR\tesseract.exe",
r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe",
]
for path in tesseract_paths:
if os.path.exists(path):
tesseract_found = True
print(f"[SETUP] ✓ Tesseract found at {path}")
break
if not tesseract_found:
# Try to find in PATH
try:
result = subprocess.run(['where', 'tesseract'], capture_output=True, text=True)
if result.returncode == 0:
tesseract_found = True
print(f"[SETUP] ✓ Tesseract found in PATH")
except:
pass
if not tesseract_found:
print("[SETUP] ✗ Tesseract OCR missing")
missing_deps.append('tesseract')
return len(missing_deps) == 0
def run_installer():
"""
Create and run the installer batch file
"""
print("\n" + "="*60)
print("MISSING DEPENDENCIES DETECTED")
print("="*60)
# Create batch file
batch_file = create_installer_batch()
if not batch_file:
show_error_dialog()
return False
print(f"\n[SETUP] Running installer: {batch_file}")
print("[SETUP] Please follow the instructions in the installer window...")
try:
# Run batch file
if IS_COMPILED:
batch_path = os.path.join(os.path.dirname(sys.executable), batch_file)
else:
batch_path = batch_file
subprocess.run([batch_path], shell=True)
# Show restart message
show_restart_dialog()
return True
except Exception as e:
print(f"[ERROR] Failed to run installer: {e}")
show_error_dialog()
return False
def show_restart_dialog():
"""
Show a dialog asking user to restart the application
"""
root = tk.Tk()
root.withdraw()
message = """Installation Complete!
All required dependencies have been installed.
Please restart Screen Translator for the changes to take effect.
Click OK to close the application."""
messagebox.showinfo("Installation Complete", message)
root.destroy()
sys.exit(0)
def show_error_dialog():
"""
Show error dialog with manual installation instructions
"""
root = tk.Tk()
root.withdraw()
message = """Failed to install dependencies automatically.
Please install manually:
1. Open Command Prompt as Administrator
2. Run these commands:
pip install Pillow pystray keyboard pytesseract requests deep-translator
3. Download and install Tesseract OCR from:
https://github.com/UB-Mannheim/tesseract/wiki
Then restart Screen Translator."""
messagebox.showerror("Installation Failed", message)
root.destroy()
# Main dependency check
print("="*60)
print("SCREEN TRANSLATOR - STARTING UP")
print("="*60)
if not check_dependencies():
print("\n[SETUP] Some dependencies are missing.")
print("[SETUP] Starting automatic installation...")
if not run_installer():
sys.exit(1)
else:
sys.exit(0)
print("\n[SETUP] All dependencies are installed!")
print("[SETUP] Starting application...\n")
# Now import the main application modules
import base64
import io
import json
import threading
from tkinter import ttk, filedialog
from dataclasses import dataclass
from typing import Optional
from PIL import Image, ImageTk, ImageGrab
import pystray
from PIL import ImageDraw
import keyboard
import time
import math
import traceback
import hashlib
import pytesseract
from deep_translator import GoogleTranslator
# Get Tesseract path
def get_tesseract_path():
paths = [
r"C:\Program Files\Tesseract-OCR\tesseract.exe",
r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe",
]
for path in paths:
if os.path.exists(path):
return path
return "tesseract"
pytesseract.pytesseract.tesseract_cmd = get_tesseract_path()
CONFIG_FILE = "config.json"
DEFAULT_CONFIG = {
"api_key": "",
"shortcuts": ("alt+q", "alt+x"),
"capture_theme": "classic",
"start_as_icon": False,
"src_lang": "auto",
"tgt_lang": "en",
"temperature": 0.2,
"window_geometry": "",
"hide_console": True
}
CAPTURE_THEMES = {
"classic": {
"name": "Classic (White/Black)",
"bg_outline": "black",
"bg_width": 3,
"fg_outline": "white",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"red": {
"name": "Red",
"bg_outline": "#8B0000",
"bg_width": 3,
"fg_outline": "#FF0000",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"green": {
"name": "Green",
"bg_outline": "#006400",
"bg_width": 3,
"fg_outline": "#00FF00",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"blue": {
"name": "Blue",
"bg_outline": "#00008B",
"bg_width": 3,
"fg_outline": "#0000FF",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"yellow": {
"name": "Yellow",
"bg_outline": "#8B8B00",
"bg_width": 3,
"fg_outline": "#FFFF00",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"cyan": {
"name": "Cyan",
"bg_outline": "#008B8B",
"bg_width": 3,
"fg_outline": "#00FFFF",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"magenta": {
"name": "Magenta",
"bg_outline": "#8B008B",
"bg_width": 3,
"fg_outline": "#FF00FF",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"orange": {
"name": "Orange",
"bg_outline": "#8B4500",
"bg_width": 3,
"fg_outline": "#FFA500",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"purple": {
"name": "Purple",
"bg_outline": "#4B0082",
"bg_width": 3,
"fg_outline": "#9370DB",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"green_purple": {
"name": "Green/Purple",
"bg_outline": "#800080",
"bg_width": 3,
"fg_outline": "#00FF00",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"red_blue": {
"name": "Red/Blue",
"bg_outline": "#0000FF",
"bg_width": 3,
"fg_outline": "#FF0000",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"yellow_black": {
"name": "Yellow/Black",
"bg_outline": "black",
"bg_width": 3,
"fg_outline": "#FFFF00",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"cyan_magenta": {
"name": "Cyan/Magenta",
"bg_outline": "#FF00FF",
"bg_width": 3,
"fg_outline": "#00FFFF",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"orange_teal": {
"name": "Orange/Teal",
"bg_outline": "#008080",
"bg_width": 3,
"fg_outline": "#FFA500",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"pink_aqua": {
"name": "Pink/Aquamarine",
"bg_outline": "#7FFFD4",
"bg_width": 3,
"fg_outline": "#FFC0CB",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"gold_navy": {
"name": "Gold/Navy Blue",
"bg_outline": "#000080",
"bg_width": 3,
"fg_outline": "#FFD700",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False
},
"rainbow": {
"name": "Rainbow",
"bg_outline": "black",
"bg_width": 3,
"fg_outline": "red",
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": True
}
}
LANGUAGE_MAP = {
"Auto": "auto",
"English": "en",
"Spanish": "es",
"French": "fr",
"German": "de",
"Italian": "it",
"Portuguese": "pt",
"Russian": "ru",
"Japanese": "ja",
"Chinese": "zh-CN",
"Korean": "ko",
"Arabic": "ar"
}
class OCRTranslator:
def __init__(self):
"""
Initialize OCR and Translation services
"""
pass
def extract_text_from_image(self, image):
"""
Extract text from image using Tesseract OCR
"""
try:
print("[LOG] Starting OCR with Tesseract...")
text = pytesseract.image_to_string(image)
print(f"[LOG] OCR completed, extracted {len(text)} characters")
return text.strip()
except Exception as e:
print(f"[LOG] OCR error: {str(e)}")
return f"[Error] OCR failed: {str(e)}"
def translate_text(self, text, src_lang="auto", tgt_lang="en"):
"""
Translate text using Google Translate (free, no API key required)
"""
try:
print(f"[LOG] Starting translation from {src_lang} to {tgt_lang}...")
if not text or text.startswith("[Error]"):
return text
translator = GoogleTranslator(source=src_lang, target=tgt_lang)
translated = translator.translate(text)
print(f"[LOG] Translation completed, {len(translated)} characters")
return translated
except Exception as e:
error_str = str(e)
print(f"[LOG] Translation error: {error_str}")
# Check for permission/firewall issues
if "WinError 10013" in error_str or "access" in error_str.lower() and "socket" in error_str.lower():
return "[Error] Network access blocked by Windows Firewall.\n\nPlease add this application to Windows Firewall exceptions:\n1. Open Windows Defender Firewall\n2. Click 'Allow an app through firewall'\n3. Click 'Change settings' and 'Allow another app'\n4. Browse and select this application\n5. Check both Private and Public\n6. Click OK and restart the application"
elif "ConnectionError" in error_str or "Max retries exceeded" in error_str:
return "[Error] Cannot connect to translation service.\n\nPossible causes:\n- No internet connection\n- Firewall blocking the connection\n- Google Translate temporarily unavailable\n\nPlease check your internet connection and firewall settings."
elif "timeout" in error_str.lower():
return "[Error] Translation timed out. Please try again."
else:
return f"[Error] Translation failed: {error_str[:200]}"
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Screen Translator (Tesseract + Google Translate)")
self.minsize(700, 600)
self.config_data = self._load_config()
# Hide console window if configured
if self.config_data.get("hide_console", True):
self._hide_console()
if "window_geometry" in self.config_data:
try:
self.geometry(self.config_data["window_geometry"])
except:
self.geometry("900x750")
else:
self.geometry("900x750")
self.ocr_translator = OCRTranslator()
self.screenshot_preview = None
self.screenshot_thumbnail = None
self.is_capturing = False
self.selection_window = None
self.start_x = None
self.start_y = None
self.rect_id = None
self.bg_rect_id = None
self.tray_icon = None
self._create_widgets()
self._setup_shortcuts()
self.protocol("WM_DELETE_WINDOW", self._on_closing)
self.bind("<Configure>", self._on_window_configure)
if self.config_data.get("start_as_icon", False):
self.after(100, self._minimize_to_tray)
def _load_config(self):
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r') as f:
loaded = json.load(f)
config = DEFAULT_CONFIG.copy()
config.update(loaded)
return config
except Exception as e:
print(f"Error loading config: {e}")
return DEFAULT_CONFIG.copy()
return DEFAULT_CONFIG.copy()
def _save_config(self):
try:
with open(CONFIG_FILE, 'w') as f:
json.dump(self.config_data, f, indent=2)
except Exception as e:
print(f"Error saving config: {e}")
def _create_widgets(self):
main_frame = ttk.Frame(self, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
notebook = ttk.Notebook(main_frame)
notebook.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(0, weight=1)
# Screenshot Translation Tab
self._create_screenshot_tab(notebook)
# Manual Input Tab
self._create_manual_tab(notebook)
# File Upload Tab
self._create_file_upload_tab(notebook)
# Settings Tab
self._create_settings_tab(notebook)
def _create_screenshot_tab(self, notebook):
"""Create the screenshot translation tab"""
translation_frame = ttk.Frame(notebook, padding="10")
notebook.add(translation_frame, text="Screenshot")
translation_frame.columnconfigure(0, weight=1)
translation_frame.rowconfigure(5, weight=1)
translation_frame.rowconfigure(7, weight=1)
# Screenshot preview
preview_label_frame = ttk.LabelFrame(translation_frame, text="Screenshot Preview", padding="5")
preview_label_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
self.preview_label = ttk.Label(preview_label_frame, text="No screenshot captured", anchor="center")
self.preview_label.pack(fill=tk.BOTH, expand=True)
# Capture buttons
button_frame = ttk.Frame(translation_frame)
button_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Button(button_frame, text="Capture Area (Alt+Q)", command=self._capture_area).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Capture Fullscreen (Alt+X)", command=self._capture_fullscreen).pack(side=tk.LEFT, padx=5)
# Language selection
lang_frame = ttk.Frame(translation_frame)
lang_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Label(lang_frame, text="Source:").pack(side=tk.LEFT, padx=5)
self.src_lang_var = tk.StringVar(value=self.config_data.get("src_lang", "auto"))
self.src_lang_combo = ttk.Combobox(lang_frame, textvariable=self.src_lang_var, width=15, state="readonly")
self.src_lang_combo['values'] = list(LANGUAGE_MAP.keys())
for lang_name, lang_code in LANGUAGE_MAP.items():
if lang_code == self.src_lang_var.get():
self.src_lang_var.set(lang_name)
break
self.src_lang_combo.pack(side=tk.LEFT, padx=5)
# Swap button
ttk.Button(lang_frame, text="⇄ Swap", command=self._swap_languages, width=8).pack(side=tk.LEFT, padx=5)
ttk.Label(lang_frame, text="Target:").pack(side=tk.LEFT, padx=5)
self.tgt_lang_var = tk.StringVar(value=self.config_data.get("tgt_lang", "en"))
self.tgt_lang_combo = ttk.Combobox(lang_frame, textvariable=self.tgt_lang_var, width=15, state="readonly")
self.tgt_lang_combo['values'] = [k for k in LANGUAGE_MAP.keys() if k != "Auto"]
for lang_name, lang_code in LANGUAGE_MAP.items():
if lang_code == self.tgt_lang_var.get():
self.tgt_lang_var.set(lang_name)
break
self.tgt_lang_combo.pack(side=tk.LEFT, padx=5)
self.src_lang_combo.bind('<<ComboboxSelected>>', lambda e: self._on_language_change())
self.tgt_lang_combo.bind('<<ComboboxSelected>>', lambda e: self._on_language_change())
# Action buttons
action_frame = ttk.Frame(translation_frame)
action_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Button(action_frame, text="Clear", command=self._clear_texts).pack(side=tk.LEFT, padx=5)
ttk.Button(action_frame, text="Copy Original", command=lambda: self._copy_to_clipboard(self.source_text)).pack(side=tk.LEFT, padx=5)
ttk.Button(action_frame, text="Copy Translation", command=lambda: self._copy_to_clipboard(self.target_text)).pack(side=tk.LEFT, padx=5)
ttk.Button(action_frame, text="Minimize to Tray", command=self._minimize_to_tray).pack(side=tk.LEFT, padx=5)
# Source text
ttk.Label(translation_frame, text="Original Text:").grid(row=4, column=0, sticky=tk.W, pady=(5, 2))
source_text_frame = ttk.Frame(translation_frame)
source_text_frame.grid(row=5, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
source_text_frame.columnconfigure(0, weight=1)
source_text_frame.rowconfigure(0, weight=1)
self.source_text = tk.Text(source_text_frame, wrap=tk.WORD, height=10)
self.source_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
source_scrollbar = ttk.Scrollbar(source_text_frame, orient=tk.VERTICAL, command=self.source_text.yview)
source_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.source_text['yscrollcommand'] = source_scrollbar.set
# Target text
ttk.Label(translation_frame, text="Translated Text:").grid(row=6, column=0, sticky=tk.W, pady=(10, 2))
target_text_frame = ttk.Frame(translation_frame)
target_text_frame.grid(row=7, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
target_text_frame.columnconfigure(0, weight=1)
target_text_frame.rowconfigure(0, weight=1)
self.target_text = tk.Text(target_text_frame, wrap=tk.WORD, height=10)
self.target_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
target_scrollbar = ttk.Scrollbar(target_text_frame, orient=tk.VERTICAL, command=self.target_text.yview)
target_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.target_text['yscrollcommand'] = target_scrollbar.set
def _create_manual_tab(self, notebook):
"""Create manual text input tab"""
manual_frame = ttk.Frame(notebook, padding="10")
notebook.add(manual_frame, text="Manual Input")
manual_frame.columnconfigure(0, weight=1)
manual_frame.rowconfigure(2, weight=1)
manual_frame.rowconfigure(4, weight=1)
# Instructions
info_label = ttk.Label(manual_frame, text="Type or paste text below to translate:", font=('Arial', 10))
info_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 10))
# Input text
ttk.Label(manual_frame, text="Input Text:").grid(row=1, column=0, sticky=tk.W, pady=(5, 2))
input_frame = ttk.Frame(manual_frame)
input_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
input_frame.columnconfigure(0, weight=1)
input_frame.rowconfigure(0, weight=1)
self.manual_input_text = tk.Text(input_frame, wrap=tk.WORD, height=10)
self.manual_input_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
input_scrollbar = ttk.Scrollbar(input_frame, orient=tk.VERTICAL, command=self.manual_input_text.yview)
input_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.manual_input_text['yscrollcommand'] = input_scrollbar.set
# Translate button
btn_frame = ttk.Frame(manual_frame)
btn_frame.grid(row=3, column=0, pady=10)
ttk.Button(btn_frame, text="Translate", command=self._translate_manual_text, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Clear All", command=self._clear_manual_texts, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Copy Translation", command=lambda: self._copy_to_clipboard(self.manual_output_text), width=15).pack(side=tk.LEFT, padx=5)
# Output text
ttk.Label(manual_frame, text="Translation:").grid(row=4, column=0, sticky=tk.W, pady=(10, 2))
output_frame = ttk.Frame(manual_frame)
output_frame.grid(row=5, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
output_frame.columnconfigure(0, weight=1)
output_frame.rowconfigure(0, weight=1)
manual_frame.rowconfigure(5, weight=1)
self.manual_output_text = tk.Text(output_frame, wrap=tk.WORD, height=10)
self.manual_output_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
output_scrollbar = ttk.Scrollbar(output_frame, orient=tk.VERTICAL, command=self.manual_output_text.yview)
output_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.manual_output_text['yscrollcommand'] = output_scrollbar.set
def _create_file_upload_tab(self, notebook):
"""Create file upload tab"""
file_frame = ttk.Frame(notebook, padding="10")
notebook.add(file_frame, text="File Upload")
file_frame.columnconfigure(0, weight=1)
file_frame.rowconfigure(3, weight=1)
file_frame.rowconfigure(5, weight=1)
# Instructions
info_label = ttk.Label(file_frame, text="Upload an image file to extract and translate text:", font=('Arial', 10))
info_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 10))
# Upload button and preview
upload_frame = ttk.Frame(file_frame)
upload_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Button(upload_frame, text="Select Image File", command=self._upload_image_file).pack(side=tk.LEFT, padx=5)
self.file_name_label = ttk.Label(upload_frame, text="No file selected")
self.file_name_label.pack(side=tk.LEFT, padx=10)
# Image preview
preview_frame = ttk.LabelFrame(file_frame, text="Image Preview", padding="5")
preview_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
self.file_preview_label = ttk.Label(preview_frame, text="No image loaded", anchor="center")
self.file_preview_label.pack(fill=tk.BOTH, expand=True)
# Extracted text
ttk.Label(file_frame, text="Extracted Text:").grid(row=3, column=0, sticky=tk.W, pady=(5, 2))
file_source_frame = ttk.Frame(file_frame)
file_source_frame.grid(row=4, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
file_source_frame.columnconfigure(0, weight=1)
file_source_frame.rowconfigure(0, weight=1)
file_frame.rowconfigure(4, weight=1)
self.file_source_text = tk.Text(file_source_frame, wrap=tk.WORD, height=8)
self.file_source_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
file_source_scrollbar = ttk.Scrollbar(file_source_frame, orient=tk.VERTICAL, command=self.file_source_text.yview)
file_source_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.file_source_text['yscrollcommand'] = file_source_scrollbar.set
# Translated text
ttk.Label(file_frame, text="Translated Text:").grid(row=5, column=0, sticky=tk.W, pady=(10, 2))
file_target_frame = ttk.Frame(file_frame)
file_target_frame.grid(row=6, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
file_target_frame.columnconfigure(0, weight=1)
file_target_frame.rowconfigure(0, weight=1)
file_frame.rowconfigure(6, weight=1)
self.file_target_text = tk.Text(file_target_frame, wrap=tk.WORD, height=8)
self.file_target_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
file_target_scrollbar = ttk.Scrollbar(file_target_frame, orient=tk.VERTICAL, command=self.file_target_text.yview)
file_target_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.file_target_text['yscrollcommand'] = file_target_scrollbar.set
# Action buttons
file_btn_frame = ttk.Frame(file_frame)
file_btn_frame.grid(row=7, column=0, pady=10)
ttk.Button(file_btn_frame, text="Copy Original", command=lambda: self._copy_to_clipboard(self.file_source_text)).pack(side=tk.LEFT, padx=5)
ttk.Button(file_btn_frame, text="Copy Translation", command=lambda: self._copy_to_clipboard(self.file_target_text)).pack(side=tk.LEFT, padx=5)
ttk.Button(file_btn_frame, text="Clear", command=self._clear_file_texts).pack(side=tk.LEFT, padx=5)
def _create_settings_tab(self, notebook):
"""Create settings tab"""
settings_frame = ttk.Frame(notebook, padding="10")
notebook.add(settings_frame, text="Settings")
# Service info
info_frame = ttk.LabelFrame(settings_frame, text="Service Information", padding="10")
info_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 15))
info_text = "OCR: Tesseract (Free, Local)\nTranslation: Google Translate (Free, No API Key)\n\nNo API key or account required!"
ttk.Label(info_frame, text=info_text, justify=tk.LEFT).pack(anchor=tk.W)
# Shortcuts
ttk.Label(settings_frame, text="Shortcuts:").grid(row=1, column=0, sticky=tk.W, pady=5)
shortcuts_frame = ttk.Frame(settings_frame)
shortcuts_frame.grid(row=1, column=1, columnspan=3, sticky=(tk.W, tk.E), pady=5)
ttk.Label(shortcuts_frame, text="Capture Area:").pack(side=tk.LEFT, padx=5)
self.shortcut1_var = tk.StringVar(value=self.config_data.get("shortcuts", ("alt+q", "alt+x"))[0])
ttk.Entry(shortcuts_frame, textvariable=self.shortcut1_var, width=15).pack(side=tk.LEFT, padx=5)
ttk.Label(shortcuts_frame, text="Fullscreen:").pack(side=tk.LEFT, padx=5)
self.shortcut2_var = tk.StringVar(value=self.config_data.get("shortcuts", ("alt+q", "alt+x"))[1])
ttk.Entry(shortcuts_frame, textvariable=self.shortcut2_var, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(shortcuts_frame, text="Save", command=self._save_shortcuts).pack(side=tk.LEFT, padx=5)
# Capture Theme
ttk.Label(settings_frame, text="Capture Theme:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.theme_var = tk.StringVar(value=self.config_data.get("capture_theme", "classic"))
theme_combo = ttk.Combobox(settings_frame, textvariable=self.theme_var, width=30, state="readonly")
theme_combo['values'] = [CAPTURE_THEMES[key]["name"] for key in CAPTURE_THEMES]
current_theme_key = self.config_data.get("capture_theme", "classic")
theme_combo.set(CAPTURE_THEMES[current_theme_key]["name"])
theme_combo.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5, padx=5)
def on_theme_change(event):
selected_name = self.theme_var.get()
for key, value in CAPTURE_THEMES.items():
if value["name"] == selected_name:
self.config_data["capture_theme"] = key
self._save_config()
break
theme_combo.bind('<<ComboboxSelected>>', on_theme_change)
# Temperature slider
ttk.Label(settings_frame, text="Translation Temperature:").grid(row=3, column=0, sticky=tk.W, pady=5)
temp_frame = ttk.Frame(settings_frame)
temp_frame.grid(row=3, column=1, columnspan=3, sticky=(tk.W, tk.E), pady=5)
self.temperature_var = tk.DoubleVar(value=self.config_data.get("temperature", 0.2))
temp_slider = ttk.Scale(temp_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL,
variable=self.temperature_var, command=self._on_temperature_change)
temp_slider.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.temp_label = ttk.Label(temp_frame, text=f"{self.temperature_var.get():.2f}")
self.temp_label.pack(side=tk.LEFT, padx=5)
ttk.Label(settings_frame, text="(Lower = More literal, Higher = More creative)",
font=('Arial', 8)).grid(row=4, column=1, columnspan=3, sticky=tk.W, pady=(0, 10))
# Start as Icon
self.start_as_icon_var = tk.BooleanVar(value=self.config_data.get("start_as_icon", False))
ttk.Checkbutton(settings_frame, text="Start minimized to tray",
variable=self.start_as_icon_var,
command=self._save_start_as_icon).grid(row=5, column=0, columnspan=2, sticky=tk.W, pady=5)
# Hide Console
self.hide_console_var = tk.BooleanVar(value=self.config_data.get("hide_console", True))
ttk.Checkbutton(settings_frame, text="Hide console window (requires restart)",
variable=self.hide_console_var,
command=self._save_hide_console).grid(row=6, column=0, columnspan=2, sticky=tk.W, pady=5)
settings_frame.columnconfigure(1, weight=1)
def _on_temperature_change(self, value):
"""Handle temperature slider change"""
temp = float(value)
self.temp_label.config(text=f"{temp:.2f}")
self.config_data["temperature"] = temp
self._save_config()
def _swap_languages(self):
"""Swap source and target languages"""
src = self.src_lang_var.get()
tgt = self.tgt_lang_var.get()
if src != "Auto":
self.src_lang_var.set(tgt)
self.tgt_lang_var.set(src)
self._on_language_change()
def _copy_to_clipboard(self, text_widget):
"""Copy text from widget to clipboard"""
try:
text = text_widget.get("1.0", tk.END).strip()
if text:
self.clipboard_clear()
self.clipboard_append(text)
messagebox.showinfo("Success", "Text copied to clipboard!")
else:
messagebox.showwarning("Warning", "No text to copy!")
except Exception as e:
messagebox.showerror("Error", f"Failed to copy: {e}")
def _clear_texts(self):
"""Clear screenshot tab texts"""
self.source_text.delete("1.0", tk.END)
self.target_text.delete("1.0", tk.END)
self.screenshot_preview = None
self.preview_label.config(image='', text="No screenshot captured")
def _clear_manual_texts(self):
"""Clear manual input tab texts"""
self.manual_input_text.delete("1.0", tk.END)
self.manual_output_text.delete("1.0", tk.END)
def _clear_file_texts(self):
"""Clear file upload tab texts"""
self.file_source_text.delete("1.0", tk.END)
self.file_target_text.delete("1.0", tk.END)
self.file_name_label.config(text="No file selected")
self.file_preview_label.config(image='', text="No image loaded")
def _translate_manual_text(self):
"""Translate manually entered text"""
input_text = self.manual_input_text.get("1.0", tk.END).strip()
if not input_text:
messagebox.showwarning("Warning", "Please enter some text to translate!")
return
self.manual_output_text.delete("1.0", tk.END)
self.manual_output_text.insert("1.0", "Translating...")
self.update()
def translate_thread():
try:
src_lang_name = self.src_lang_var.get()
tgt_lang_name = self.tgt_lang_var.get()
src_lang = LANGUAGE_MAP.get(src_lang_name, "auto")
tgt_lang = LANGUAGE_MAP.get(tgt_lang_name, "en")
translated = self.ocr_translator.translate_text(input_text, src_lang, tgt_lang)
self.manual_output_text.delete("1.0", tk.END)
self.manual_output_text.insert("1.0", translated)
except Exception as e:
error_text = f"[Error] Translation failed: {str(e)}"
self.manual_output_text.delete("1.0", tk.END)
self.manual_output_text.insert("1.0", error_text)
threading.Thread(target=translate_thread, daemon=True).start()
def _upload_image_file(self):
"""Upload and process an image file"""
file_path = filedialog.askopenfilename(
title="Select Image File",
filetypes=[
("Image files", "*.png *.jpg *.jpeg *.bmp *.gif *.tiff"),
("All files", "*.*")
]
)
if not file_path:
return
try:
# Load and display image
image = Image.open(file_path)
self.file_name_label.config(text=os.path.basename(file_path))
# Create thumbnail for preview
thumbnail = image.copy()
thumbnail.thumbnail((350, 350), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(thumbnail)
self.file_preview_label.config(image=photo, text="")
self.file_preview_label.image = photo # Keep reference
# Clear previous texts
self.file_source_text.delete("1.0", tk.END)
self.file_target_text.delete("1.0", tk.END)
# Show processing message
self.file_source_text.insert("1.0", "Extracting text...")
self.file_target_text.insert("1.0", "Waiting...")
self.update()
# Process in thread
def process_thread():
try:
# Extract text
original_text = self.ocr_translator.extract_text_from_image(image)
self.file_source_text.delete("1.0", tk.END)
self.file_source_text.insert("1.0", original_text)
self.update()
# Translate
if original_text and not original_text.startswith("[Error]"):
src_lang_name = self.src_lang_var.get()
tgt_lang_name = self.tgt_lang_var.get()
src_lang = LANGUAGE_MAP.get(src_lang_name, "auto")
tgt_lang = LANGUAGE_MAP.get(tgt_lang_name, "en")
translated = self.ocr_translator.translate_text(original_text, src_lang, tgt_lang)
self.file_target_text.delete("1.0", tk.END)
self.file_target_text.insert("1.0", translated)
else:
self.file_target_text.delete("1.0", tk.END)
self.file_target_text.insert("1.0", "[Error] No text extracted")
except Exception as e:
error_msg = f"[Error] Processing failed: {str(e)}"
self.file_source_text.delete("1.0", tk.END)
self.file_source_text.insert("1.0", error_msg)
self.file_target_text.delete("1.0", tk.END)
threading.Thread(target=process_thread, daemon=True).start()
except Exception as e:
messagebox.showerror("Error", f"Failed to load image: {e}")
def _on_language_change(self):
"""Save language selections"""
src_lang_name = self.src_lang_var.get()
tgt_lang_name = self.tgt_lang_var.get()
self.config_data["src_lang"] = LANGUAGE_MAP.get(src_lang_name, "auto")
self.config_data["tgt_lang"] = LANGUAGE_MAP.get(tgt_lang_name, "en")
self._save_config()
def _save_shortcuts(self):
shortcut1 = self.shortcut1_var.get().strip()
shortcut2 = self.shortcut2_var.get().strip()
if shortcut1 and shortcut2:
self.config_data["shortcuts"] = (shortcut1, shortcut2)
self._save_config()
self._setup_shortcuts()
messagebox.showinfo("Success", "Shortcuts saved! Restart app for changes.")
else:
messagebox.showwarning("Warning", "Please enter both shortcuts.")
def _save_start_as_icon(self):
self.config_data["start_as_icon"] = self.start_as_icon_var.get()
self._save_config()
def _save_hide_console(self):
self.config_data["hide_console"] = self.hide_console_var.get()
self._save_config()
messagebox.showinfo("Info", "Console visibility will change on next restart.")
def _hide_console(self):
"""Hide the console window on Windows"""
try:
import ctypes
import sys
# Only works on Windows
if sys.platform == 'win32':
# Get console window handle
console_window = ctypes.windll.kernel32.GetConsoleWindow()
if console_window:
# SW_HIDE = 0
ctypes.windll.user32.ShowWindow(console_window, 0)
print("[INFO] Console window hidden")
except Exception as e:
print(f"[WARNING] Could not hide console: {e}")
def _setup_shortcuts(self):
shortcuts = self.config_data.get("shortcuts", ("alt+q", "alt+x"))
try:
keyboard.add_hotkey(shortcuts[0], self._capture_area)
keyboard.add_hotkey(shortcuts[1], self._capture_fullscreen)
except Exception as e:
print(f"Error setting up shortcuts: {e}")
def _capture_area(self):
if self.is_capturing:
return
self.is_capturing = True
self.withdraw()
self.after(200, self._show_selection_overlay)
def _show_selection_overlay(self):
self.selection_window = tk.Toplevel(self)
self.selection_window.attributes('-fullscreen', True)
self.selection_window.attributes('-alpha', 0.3)
self.selection_window.attributes('-topmost', True)
canvas = tk.Canvas(self.selection_window, cursor="cross", bg='grey', highlightthickness=0)
canvas.pack(fill=tk.BOTH, expand=True)
self.start_x = None
self.start_y = None
self.rect_id = None
self.bg_rect_id = None
theme_key = self.config_data.get("capture_theme", "classic")
theme = CAPTURE_THEMES[theme_key]
rainbow_colors = ["red", "orange", "yellow", "green", "cyan", "blue", "purple", "magenta"]
current_color_index = [0]
def update_rainbow():
if theme["rainbow"] and self.rect_id and canvas.winfo_exists():
current_color_index[0] = (current_color_index[0] + 1) % len(rainbow_colors)
canvas.itemconfig(self.rect_id, outline=rainbow_colors[current_color_index[0]])
canvas.after(100, update_rainbow)
def on_mouse_down(event):
self.start_x = event.x
self.start_y = event.y
self.bg_rect_id = canvas.create_rectangle(
self.start_x, self.start_y, self.start_x, self.start_y,
outline=theme["bg_outline"],
width=theme["bg_width"]
)
self.rect_id = canvas.create_rectangle(
self.start_x, self.start_y, self.start_x, self.start_y,
outline=theme["fg_outline"],
width=theme["fg_width"],
dash=theme["fg_dash"]
)
if theme["rainbow"]:
update_rainbow()
def on_mouse_move(event):
if self.start_x and self.start_y and self.rect_id:
canvas.coords(self.rect_id, self.start_x, self.start_y, event.x, event.y)
if self.bg_rect_id:
canvas.coords(self.bg_rect_id, self.start_x, self.start_y, event.x, event.y)
def on_mouse_up(event):
if self.start_x and self.start_y:
x1, y1 = min(self.start_x, event.x), min(self.start_y, event.y)
x2, y2 = max(self.start_x, event.x), max(self.start_y, event.y)
self.selection_window.destroy()
self._capture_screen_area(x1, y1, x2, y2)
def on_right_click(event):
self._cancel_selection()
def on_escape(event):
self._cancel_selection()
canvas.bind("<ButtonPress-1>", on_mouse_down)
canvas.bind("<B1-Motion>", on_mouse_move)
canvas.bind("<ButtonRelease-1>", on_mouse_up)
canvas.bind("<ButtonPress-3>", on_right_click)
canvas.bind("<Escape>", on_escape)
canvas.focus_set()
def _cancel_selection(self):
if self.selection_window:
self.selection_window.destroy()
self.selection_window = None
self.is_capturing = False
self.deiconify()
def _capture_screen_area(self, x1, y1, x2, y2):
try:
screenshot = ImageGrab.grab(bbox=(x1, y1, x2, y2))
self._process_screenshot(screenshot)
except Exception as e:
messagebox.showerror("Error", f"Failed to capture: {e}")
finally:
self.is_capturing = False
self.deiconify()
def _capture_fullscreen(self):
if self.is_capturing:
return
self.is_capturing = True
self.withdraw()
self.after(200, self._do_fullscreen_capture)
def _do_fullscreen_capture(self):
try:
screenshot = ImageGrab.grab()
self._process_screenshot(screenshot)
except Exception as e:
messagebox.showerror("Error", f"Failed to capture: {e}")
finally:
self.is_capturing = False
self.deiconify()
def _process_screenshot(self, screenshot):
self.screenshot_preview = screenshot
self._update_screenshot_preview()
self.source_text.delete("1.0", tk.END)
self.source_text.insert("1.0", "Processing screenshot...")
self.target_text.delete("1.0", tk.END)
self.target_text.insert("1.0", "Waiting for translation...")
self.update()
def process_thread():
try:
print("[LOG] Starting screenshot processing thread")
src_lang_name = self.src_lang_var.get()
tgt_lang_name = self.tgt_lang_var.get()
src_lang = LANGUAGE_MAP.get(src_lang_name, "auto")
tgt_lang = LANGUAGE_MAP.get(tgt_lang_name, "en")
print(f"[LOG] Languages - Source: {src_lang}, Target: {tgt_lang}")
print("[LOG] Calling Tesseract OCR...")
original_text = self.ocr_translator.extract_text_from_image(screenshot)
print(f"[LOG] OCR completed, text length: {len(original_text)}")
self.source_text.delete("1.0", tk.END)
self.source_text.insert("1.0", original_text)
self.update()
if original_text and not original_text.startswith("[Error]"):
print("[LOG] Text extracted successfully, starting translation...")
translated_text = self.ocr_translator.translate_text(original_text, src_lang, tgt_lang)
print(f"[LOG] Translation completed, text length: {len(translated_text)}")
self.target_text.delete("1.0", tk.END)
self.target_text.insert("1.0", translated_text)
else:
print("[LOG] OCR failed or returned error, skipping translation")
self.target_text.delete("1.0", tk.END)
self.target_text.insert("1.0", "[Error] No text extracted from image")
print("[LOG] Screenshot processing completed")
except Exception as e:
print(f"[LOG] Exception in process_thread: {str(e)}")
print(f"[LOG] Traceback: {traceback.format_exc()}")
error_text = f"[Error] Processing failed: {str(e)}"
self.source_text.delete("1.0", tk.END)
self.source_text.insert("1.0", error_text)
self.target_text.delete("1.0", tk.END)
threading.Thread(target=process_thread, daemon=True).start()
def _update_screenshot_preview(self):
if not self.screenshot_preview:
return
try:
thumbnail = self.screenshot_preview.copy()
thumbnail.thumbnail((350, 350), Image.Resampling.LANCZOS)
self.screenshot_thumbnail = ImageTk.PhotoImage(thumbnail)
self.preview_label.config(image=self.screenshot_thumbnail, text="")
except Exception as e:
print(f"Error updating preview: {e}")
def _create_tray_icon(self):
image = Image.new('RGB', (64, 64), color='blue')
draw = ImageDraw.Draw(image)
draw.rectangle([16, 16, 48, 48], fill='white')
menu = pystray.Menu(
pystray.MenuItem("Show", self._show_window),
pystray.MenuItem("Exit", self._exit_app)
)
self.tray_icon = pystray.Icon("screen_translator", image, "Screen Translator", menu)
threading.Thread(target=self.tray_icon.run, daemon=True).start()
def _minimize_to_tray(self):
self.withdraw()
if not self.tray_icon:
self._create_tray_icon()
def _show_window(self):
self.deiconify()
self.lift()
self.focus_force()
def _exit_app(self):
self._save_window_geometry()
if self.tray_icon:
self.tray_icon.stop()
self.quit()
def _on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self._exit_app()
def _on_window_configure(self, event):
if event.widget == self:
if hasattr(self, '_geometry_save_timer'):
self.after_cancel(self._geometry_save_timer)
self._geometry_save_timer = self.after(500, self._save_window_geometry)
def _save_window_geometry(self):
try:
geometry = self.geometry()
self.config_data["window_geometry"] = geometry
self._save_config()
except Exception as e:
print(f"Error saving window geometry: {e}")
if __name__ == "__main__":
print("="*60)
print("STARTING APPLICATION")
print("="*60)
app = App()
app.mainloop()
Commenti
Posta un commento