()
| 223 | # ── 交互模式 ─────────────────────────────────────────────────────────── |
| 224 | |
| 225 | def run_interactive(): |
| 226 | try: |
| 227 | import questionary |
| 228 | from rich.console import Console |
| 229 | from rich.panel import Panel |
| 230 | from rich.text import Text |
| 231 | except ImportError: |
| 232 | print("交互模式需要 rich 和 questionary:pip install rich questionary") |
| 233 | sys.exit(1) |
| 234 | |
| 235 | console = Console() |
| 236 | |
| 237 | def _header(): |
| 238 | console.print(Panel( |
| 239 | Text("StegaPy — 图像隐写与数字水印工具", justify="center", style="bold cyan"), |
| 240 | subtitle="[dim]无参数启动 = 交互模式 | python cli.py --help 查看脚本用法[/dim]", |
| 241 | border_style="cyan", |
| 242 | )) |
| 243 | |
| 244 | def _ask_file(msg: str, must_exist: bool = True) -> str: |
| 245 | while True: |
| 246 | path = questionary.path(msg, only_directories=False).ask() |
| 247 | if path is None: |
| 248 | raise KeyboardInterrupt |
| 249 | path = path.strip() |
| 250 | if must_exist and not os.path.isfile(path): |
| 251 | console.print(f"[red]文件不存在:{path}[/red]") |
| 252 | else: |
| 253 | return path |
| 254 | |
| 255 | def _ask_dir(msg: str) -> str: |
| 256 | path = questionary.path(msg, only_directories=True).ask() |
| 257 | if path is None: |
| 258 | raise KeyboardInterrupt |
| 259 | return path.strip() or "." |
| 260 | |
| 261 | def _ask_password(required: bool = False) -> str: |
| 262 | hint = "密码(必填)" if required else "密码(留空则不加密)" |
| 263 | pwd = questionary.password(hint).ask() |
| 264 | if pwd is None: |
| 265 | raise KeyboardInterrupt |
| 266 | return pwd or None |
| 267 | |
| 268 | MENU = { |
| 269 | "📥 数据隐写 — 嵌入": "embed", |
| 270 | "📤 数据隐写 — 提取": "extract", |
| 271 | "🔏 数字水印 — 生成签名": "wm_gensig", |
| 272 | "💧 数字水印 — 嵌入": "wm_embed", |
| 273 | "🔍 数字水印 — 验证": "wm_verify", |
| 274 | "─────────────────────": None, |
| 275 | "退出": "quit", |
| 276 | } |
| 277 | |
| 278 | while True: |
| 279 | console.print() |
| 280 | _header() |
| 281 | |
| 282 | choice = questionary.select( |
no test coverage detected