Skip to content

Commit a381ae3

Browse files
committed
Build Windows
1 parent 2de2c09 commit a381ae3

File tree

12 files changed

+622
-83
lines changed

12 files changed

+622
-83
lines changed

TranslateBook.spec

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
import os
3+
import tiktoken_ext.openai_public
4+
5+
block_cipher = None
6+
7+
# Get tiktoken_ext data directory (contains pre-bundled encodings)
8+
tiktoken_ext_dir = os.path.dirname(tiktoken_ext.openai_public.__file__)
9+
10+
# Prepare datas list
11+
datas_list = [
12+
('src/web/static', 'src/web/static'),
13+
('src/web/templates', 'src/web/templates'),
14+
('src', 'src'),
15+
('.env.example', '.'),
16+
]
17+
18+
# Add tiktoken_ext if directory exists
19+
if os.path.exists(tiktoken_ext_dir):
20+
datas_list.append((tiktoken_ext_dir, 'tiktoken_ext/openai_public'))
21+
22+
a = Analysis(
23+
['launcher.py'],
24+
pathex=[],
25+
binaries=[],
26+
datas=datas_list,
27+
hiddenimports=[
28+
'flask',
29+
'flask_cors',
30+
'flask_socketio',
31+
'python_socketio',
32+
'socketio',
33+
'engineio',
34+
'engineio.async_drivers.threading',
35+
'requests',
36+
'tqdm',
37+
'httpx',
38+
'lxml',
39+
'lxml.etree',
40+
'lxml._elementpath',
41+
'dotenv',
42+
'aiofiles',
43+
'tiktoken',
44+
'tiktoken_ext',
45+
'tiktoken_ext.openai_public',
46+
'pyyaml',
47+
'jinja2',
48+
'langdetect',
49+
'PIL',
50+
'dns',
51+
'dns.resolver',
52+
],
53+
hookspath=[],
54+
hooksconfig={},
55+
runtime_hooks=[],
56+
excludes=[],
57+
win_no_prefer_redirects=False,
58+
win_private_assemblies=False,
59+
cipher=block_cipher,
60+
noarchive=False,
61+
)
62+
63+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
64+
65+
exe = EXE(
66+
pyz,
67+
a.scripts,
68+
a.binaries,
69+
a.zipfiles,
70+
a.datas,
71+
[],
72+
name='TranslateBook',
73+
debug=False,
74+
bootloader_ignore_signals=False,
75+
strip=False,
76+
upx=True,
77+
upx_exclude=[],
78+
runtime_tmpdir=None,
79+
console=True,
80+
disable_windowed_traceback=False,
81+
argv_emulation=False,
82+
target_arch=None,
83+
codesign_identity=None,
84+
entitlements_file=None,
85+
icon=None,
86+
)

build_exe.bat

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@echo off
2+
REM ============================================
3+
REM TranslateBook - Build Executable
4+
REM ============================================
5+
6+
echo.
7+
echo ============================================
8+
echo TranslateBook - Building Executable
9+
echo ============================================
10+
echo.
11+
12+
REM Check if virtual environment exists
13+
if not exist "venv" (
14+
echo [ERROR] Virtual environment not found
15+
echo Please run start.bat first to set up the environment
16+
pause
17+
exit /b 1
18+
)
19+
20+
REM Activate virtual environment
21+
echo [1/4] Activating virtual environment...
22+
call venv\Scripts\activate.bat
23+
24+
REM Install PyInstaller if not already installed
25+
echo [2/4] Checking PyInstaller installation...
26+
pip show pyinstaller >nul 2>&1
27+
if errorlevel 1 (
28+
echo [INFO] Installing PyInstaller...
29+
pip install pyinstaller
30+
)
31+
echo [OK] PyInstaller ready
32+
33+
REM Clean previous builds
34+
echo [3/4] Cleaning previous builds...
35+
if exist "dist" rmdir /s /q dist
36+
if exist "build" rmdir /s /q build
37+
echo [OK] Cleaned
38+
39+
REM Build executable
40+
echo [4/4] Building TranslateBook.exe...
41+
echo This may take 5-10 minutes...
42+
echo.
43+
pyinstaller --clean TranslateBook.spec
44+
45+
if errorlevel 1 (
46+
echo.
47+
echo [ERROR] Build failed
48+
pause
49+
exit /b 1
50+
)
51+
52+
echo.
53+
echo ============================================
54+
echo Build Complete!
55+
echo ============================================
56+
echo.
57+
echo Executable location: dist\TranslateBook.exe
58+
echo File size:
59+
for %%A in (dist\TranslateBook.exe) do echo %%~zA bytes (approx. %%~zA / 1048576 MB)
60+
echo.
61+
echo You can now distribute this single .exe file
62+
echo Users need to have Ollama installed separately
63+
echo.
64+
pause

launcher.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Launcher wrapper for PyInstaller executable
3+
Handles proper working directory setup and .env file management
4+
"""
5+
import os
6+
import sys
7+
import shutil
8+
from pathlib import Path
9+
10+
def setup_working_directory():
11+
"""Setup proper working directory for the executable"""
12+
13+
# Determine if running as PyInstaller bundle
14+
if getattr(sys, 'frozen', False):
15+
# Running as compiled executable
16+
exe_dir = Path(sys.executable).parent
17+
18+
# Create a data directory next to the executable
19+
app_data_dir = exe_dir / 'TranslateBook_Data'
20+
app_data_dir.mkdir(exist_ok=True)
21+
22+
# Change working directory to app data folder
23+
os.chdir(app_data_dir)
24+
25+
# Create necessary subdirectories
26+
(app_data_dir / 'translated_files').mkdir(exist_ok=True)
27+
(app_data_dir / 'checkpoints').mkdir(exist_ok=True)
28+
29+
# Copy .env.example if it doesn't exist and is bundled
30+
env_example_path = app_data_dir / '.env.example'
31+
if not env_example_path.exists():
32+
# Check if .env.example is in the bundle
33+
bundle_dir = Path(sys._MEIPASS)
34+
bundled_env_example = bundle_dir / '.env.example'
35+
if bundled_env_example.exists():
36+
shutil.copy(bundled_env_example, env_example_path)
37+
38+
# Create default .env if it doesn't exist
39+
env_path = app_data_dir / '.env'
40+
if not env_path.exists():
41+
print("\n" + "="*70)
42+
print("FIRST RUN DETECTED")
43+
print("="*70)
44+
print("\nCreating default configuration file...")
45+
46+
# Create a minimal .env with defaults
47+
default_env = """# TranslateBook with LLM Configuration
48+
# This file was auto-generated on first run
49+
50+
# === LLM PROVIDER ===
51+
LLM_PROVIDER=ollama
52+
53+
# === OLLAMA CONFIGURATION ===
54+
API_ENDPOINT=http://localhost:11434/api/generate
55+
DEFAULT_MODEL=qwen3:14b
56+
OLLAMA_NUM_CTX=4096
57+
58+
# === SERVER CONFIGURATION ===
59+
PORT=5000
60+
HOST=127.0.0.1
61+
OUTPUT_DIR=translated_files
62+
63+
# === OPTIONAL: CLOUD PROVIDERS ===
64+
# Uncomment and add your API keys if using cloud providers
65+
# OPENROUTER_API_KEY=sk-or-v1-...
66+
# OPENAI_API_KEY=sk-...
67+
# GEMINI_API_KEY=...
68+
69+
# === PERFORMANCE ===
70+
REQUEST_TIMEOUT=900
71+
MAX_TOKENS_PER_CHUNK=400
72+
"""
73+
env_path.write_text(default_env, encoding='utf-8')
74+
print(f"[OK] Configuration file created at: {env_path}")
75+
print("\n[INFO] You can edit this file later to customize settings")
76+
print("="*70)
77+
print()
78+
79+
print(f"[INFO] Working directory: {app_data_dir}")
80+
print(f"[INFO] Translated files will be saved to: {app_data_dir / 'translated_files'}")
81+
print()
82+
83+
else:
84+
# Running as normal Python script
85+
print("[DEV] Running as Python script (development mode)")
86+
87+
if __name__ == '__main__':
88+
try:
89+
# Setup environment
90+
setup_working_directory()
91+
92+
# Import and start the server
93+
from translation_api import start_server
94+
start_server()
95+
96+
except KeyboardInterrupt:
97+
print("\n\n[STOPPED] Server stopped by user")
98+
sys.exit(0)
99+
except Exception as e:
100+
print("\n" + "="*70)
101+
print("[ERROR] STARTUP ERROR")
102+
print("="*70)
103+
print(f"\n{e}\n")
104+
import traceback
105+
traceback.print_exc()
106+
print("\nPress Enter to exit...")
107+
input()
108+
sys.exit(1)

src/api/blueprints/config_routes.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22
Configuration and health check routes
33
"""
44
import os
5+
import sys
56
import asyncio
67
import logging
78
import requests
89
import re
10+
import time
911
from flask import Blueprint, request, jsonify, send_from_directory
1012
from pathlib import Path
1113

1214

1315
def get_base_path():
1416
"""Get base path for resources (templates, static files)"""
17+
# In PyInstaller bundle, use the temporary extraction directory
18+
if getattr(sys, 'frozen', False):
19+
return sys._MEIPASS
1520
return os.getcwd()
1621

1722

@@ -46,6 +51,9 @@ def create_config_blueprint():
4651
"""Create and configure the config blueprint"""
4752
bp = Blueprint('config', __name__)
4853

54+
# Store server startup time to detect restarts
55+
startup_time = int(time.time())
56+
4957
@bp.route('/')
5058
def serve_interface():
5159
"""Serve the main translation interface"""
@@ -64,7 +72,8 @@ def health_check():
6472
"message": "Translation API is running",
6573
"translate_module": "loaded",
6674
"ollama_default_endpoint": DEFAULT_OLLAMA_API_ENDPOINT,
67-
"supported_formats": ["txt", "epub", "srt"]
75+
"supported_formats": ["txt", "epub", "srt"],
76+
"startup_time": startup_time # Used to detect server restarts
6877
})
6978

7079
@bp.route('/api/models', methods=['GET', 'POST'])

src/config.py

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,41 +34,50 @@
3434
_config_logger.debug(f"📁 .env exists: {_env_exists}")
3535

3636
if not _env_exists:
37-
print("\n" + "="*70)
38-
print("⚠️ WARNING: .env configuration file not found")
39-
print("="*70)
40-
print("\nThe application will run with default settings, but you may need to")
41-
print("configure it for your specific setup.\n")
42-
43-
if _env_example.exists():
44-
print("📋 QUICK SETUP:")
45-
print(f" 1. Copy the template: copy .env.example .env")
46-
print(f" 2. Edit .env to match your configuration")
47-
print(f" 3. Restart the application\n")
37+
# Check if running as PyInstaller executable
38+
_is_frozen = getattr(sys, 'frozen', False)
39+
40+
if not _is_frozen:
41+
# Only show the interactive prompt when NOT running as executable
42+
print("\n" + "="*70)
43+
print("⚠️ WARNING: .env configuration file not found")
44+
print("="*70)
45+
print("\nThe application will run with default settings, but you may need to")
46+
print("configure it for your specific setup.\n")
47+
48+
if _env_example.exists():
49+
print("📋 QUICK SETUP:")
50+
print(f" 1. Copy the template: copy .env.example .env")
51+
print(f" 2. Edit .env to match your configuration")
52+
print(f" 3. Restart the application\n")
53+
else:
54+
print("📋 MANUAL SETUP:")
55+
print(f" 1. Create a .env file in: {Path.cwd()}")
56+
print(f" 2. Add your configuration (see documentation)")
57+
print(f" 3. Restart the application\n")
58+
59+
print("🔧 DEFAULT SETTINGS BEING USED:")
60+
print(f" • API Endpoint: http://localhost:11434/api/generate")
61+
print(f" • LLM Provider: ollama")
62+
print(f" • Model: qwen3:14b")
63+
print(f" • Port: 5000")
64+
print(f"\n💡 TIP: If using a remote server or different provider, you MUST")
65+
print(f" create a .env file with the correct settings.\n")
66+
print("="*70)
67+
print("Press Ctrl+C to stop and configure, or wait 5 seconds to continue...")
68+
print("="*70 + "\n")
69+
70+
# Give user time to read and react
71+
import time
72+
try:
73+
time.sleep(5)
74+
except KeyboardInterrupt:
75+
print("\n\n⏹️ Startup cancelled by user. Please configure .env and try again.\n")
76+
sys.exit(0)
4877
else:
49-
print("📋 MANUAL SETUP:")
50-
print(f" 1. Create a .env file in: {Path.cwd()}")
51-
print(f" 2. Add your configuration (see documentation)")
52-
print(f" 3. Restart the application\n")
53-
54-
print("🔧 DEFAULT SETTINGS BEING USED:")
55-
print(f" • API Endpoint: http://localhost:11434/api/generate")
56-
print(f" • LLM Provider: ollama")
57-
print(f" • Model: qwen3:14b")
58-
print(f" • Port: 5000")
59-
print(f"\n💡 TIP: If using a remote server or different provider, you MUST")
60-
print(f" create a .env file with the correct settings.\n")
61-
print("="*70)
62-
print("Press Ctrl+C to stop and configure, or wait 5 seconds to continue...")
63-
print("="*70 + "\n")
64-
65-
# Give user time to read and react
66-
import time
67-
try:
68-
time.sleep(5)
69-
except KeyboardInterrupt:
70-
print("\n\n⏹️ Startup cancelled by user. Please configure .env and try again.\n")
71-
sys.exit(0)
78+
# Running as executable - silently use defaults
79+
if _debug_mode:
80+
_config_logger.debug("⚠️ .env not found, using defaults (executable mode)")
7281

7382
# Load .env file if it exists
7483
_dotenv_result = load_dotenv(_env_file)

0 commit comments

Comments
 (0)