-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathVideoSubtitleRemover.py
More file actions
196 lines (168 loc) · 6.93 KB
/
Copy pathVideoSubtitleRemover.py
File metadata and controls
196 lines (168 loc) · 6.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/usr/bin/env python3
"""
Video Subtitle Remover Pro
A professional Windows application for AI-powered subtitle removal from videos
and images. Based on: https://github.com/YaoFANGUK/video-subtitle-remover
Author: SysAdminDoc
See APP_VERSION for the running version -- the docstring deliberately omits
a hardcoded number so there is a single source of truth.
"""
import multiprocessing
multiprocessing.freeze_support()
import logging
import logging.handlers
import sys
import traceback
# Kept for the back-compat surface: callers and tests reach
# `VideoSubtitleRemover.datetime` as a module attribute.
from datetime import datetime # noqa: F401
# App identity and paths live in gui.config -- the single source of
# truth since the RM-114 extraction. gui.theme / gui.config import no
# tkinter, so this is safe before the GUI availability guard below.
from gui.config import (
APP_NAME, APP_VERSION, APP_AUTHOR,
LOG_DIR, LOG_FILE, SETTINGS_FILE,
)
# =============================================================================
# LOGGING SETUP -- file + stream, crash handler
# =============================================================================
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.StreamHandler(),
logging.handlers.RotatingFileHandler(
LOG_FILE, maxBytes=5 * 1024 * 1024, backupCount=2, encoding='utf-8'),
]
)
logger = logging.getLogger(__name__)
def crash_handler(exc_type, exc_value, exc_tb):
"""Global crash handler -- log to file and show MessageBox."""
msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
logger.critical(f"UNHANDLED EXCEPTION:\n{msg}")
try:
import tkinter.messagebox as mb
mb.showerror("Fatal Error",
f"{APP_NAME} crashed.\n\n{exc_value}\n\nLog: {LOG_FILE}")
except Exception:
pass
sys.__excepthook__(exc_type, exc_value, exc_tb)
sys.excepthook = crash_handler
# RM-52: opt-in crash reporting. Strictly off unless the user sets BOTH
# VSR_GLITCHTIP_DSN AND VSR_CRASH_REPORTS=1. The install() call is a
# no-op when either is missing, so default installs never phone home.
try:
from backend.crash_reporter import install as _install_crash_reporter
_install_crash_reporter()
except Exception:
pass
try:
from backend.security_checks import warn_if_vulnerable_opencv_libpng
warn_if_vulnerable_opencv_libpng(logger)
except Exception:
pass
# GUI availability guard -- fail with a readable message, not a traceback.
try:
import tkinter as tk # noqa: F401
except ImportError:
logger.error("Tkinter not found. Please install Python with Tkinter support.")
sys.exit(1)
try:
from PIL import Image, ImageTk # noqa: F401
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
logger.warning("Pillow not installed. Image preview will be limited.")
# =============================================================================
# RM-114 back-compat surface
# =============================================================================
# Everything the monolith used to define is re-exported here so legacy
# callers (`import VideoSubtitleRemover; VideoSubtitleRemover.X`) keep
# resolving. The canonical re-export list lives in gui/__init__.py --
# add new names there first.
from gui import ( # noqa: E402, F401
Theme, apply_high_contrast_theme, apply_default_theme, f, mono,
InpaintMode, ProcessingStatus, STATUS_UI, VSR_SETTINGS_FORMAT,
ProcessingConfig, QueueItem, BUILTIN_PRESETS,
_coerce_bool, _coerce_int, _coerce_float, _coerce_text,
_coerce_rect, _coerce_rect_list, _coerce_gui_mode,
_read_json_object, _write_json_atomic,
_migrate_settings, load_settings, save_settings,
PRESETS_FILE, list_presets, apply_preset,
save_user_preset, delete_user_preset, export_preset, import_preset,
status_ui,
get_app_dir, detect_gpu, format_time, format_size,
is_video_file, is_image_file,
_CURATED_LANG_NAMES, _engine_supported_languages, _build_language_list,
detect_ai_engines, detect_ffmpeg, get_file_info,
_soft_subtitle_stream_record, _format_soft_subtitle_summary,
_queue_item_info_text, truncate_middle,
format_quality_report, summarize_quality_reports,
VideoSubtitleRemoverApp,
)
from gui.widgets import ( # noqa: E402, F401
_get_dpi_scale, _scaled,
Tooltip, ModernButton, ModernProgressBar, ModernToggle,
ModernSlider, show_confirm, TaskbarProgress, make_themed_menu,
Toast, SegmentedPicker, DragDropFrame, QueueItemWidget,
TextWidgetHandler,
)
def _run_smoke_test() -> int:
"""RM-106: bundled GUI smoke path for strict release verification.
Constructs the full application off-screen, pumps one idle cycle, and
tears it down without entering the Tk mainloop. Settings are pinned to
a throwaway temp dir so a release-runner smoke does not clobber a real
user's %APPDATA% config. Returns 0 on success, 1 on any failure so the
release workflow can gate on the exit code.
"""
import tempfile
from pathlib import Path as _Path
with tempfile.TemporaryDirectory(prefix="vsr_smoke_") as tmp:
# Redirect settings persistence to the throwaway dir. gui.config is
# the single source of truth; VideoSubtitleRemover re-exports the
# name for back-compat, so update both views.
import gui.config as _gc
smoke_settings = _Path(tmp) / "settings.json"
_gc.SETTINGS_FILE = smoke_settings
global SETTINGS_FILE
SETTINGS_FILE = smoke_settings
app = None
try:
app = VideoSubtitleRemoverApp()
app.root.withdraw()
app.root.update_idletasks()
title = app.root.title()
if not title.startswith(APP_NAME):
logger.error("Smoke test: unexpected window title %r", title)
return 1
logger.info("Smoke test passed: GUI constructed and torn down.")
return 0
except Exception:
logger.critical("Smoke test failed:\n%s", traceback.format_exc())
return 1
finally:
if app is not None:
try:
app.root.destroy()
except Exception:
pass
def main():
"""Main entry point."""
# RM-106: headless self-test for release verification. Must run before
# the DPI/mainloop path so it can exit cleanly on a CI runner.
if "--smoke-test" in sys.argv[1:]:
sys.exit(_run_smoke_test())
# High DPI support on Windows -- Per-Monitor V2 for best multi-monitor support
try:
from ctypes import windll
# Try Per-Monitor V2 first (Windows 10 1703+), then fall back
try:
windll.shcore.SetProcessDpiAwareness(2)
except Exception:
windll.shcore.SetProcessDpiAwareness(1)
except Exception:
pass
app = VideoSubtitleRemoverApp()
app.run()
if __name__ == "__main__":
main()