feat(cli): добавлен пункт Transcribe в меню «Отправить» проводника
- Зачем:
- запуск транскрипции правой кнопкой из проводника Windows без
терминала, для пользователей без прав администратора.
- Что:
- новый модуль context_menu.py: установка/удаление Transcribe.cmd
в папке SendTo (OEM-кодировка, CRLF, без реестра).
- флаги --install-menu / --uninstall-menu в cli.py с ранней
валидацией до load_config; files стал необязательным аргументом.
- раздел в README.md: установка, использование, ручное удаление
через shell:sendto, известные ограничения.
- Проверка:
- uv run pytest — 191 passed, 1 skipped.
- uv run transcribe --install-menu и правый клик → Отправить →
Transcribe на файле с кириллицей в имени.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
966efc3bfe
commit
f18ad6ea2a
@@ -9,6 +9,8 @@ from rich.console import Console
|
||||
from rich.status import Status
|
||||
|
||||
from .config import apply_device_defaults, load_config, resolve_defaults
|
||||
from .context_menu import install_menu as install_context_menu
|
||||
from .context_menu import uninstall_menu as uninstall_context_menu
|
||||
from .formatter import format_transcript, write_transcript
|
||||
from .transcriber import (
|
||||
Segment,
|
||||
@@ -45,7 +47,7 @@ def _format_device_info(device_used: str) -> str:
|
||||
|
||||
@app.command()
|
||||
def main(
|
||||
files: list[Path] = typer.Argument(..., help="Пути к аудио/видеофайлам"),
|
||||
files: list[Path] | None = typer.Argument(None, help="Пути к аудио/видеофайлам"),
|
||||
model: str | None = typer.Option(
|
||||
None, "--model", "-m", show_default=False, help="Модель Whisper [по умолч.: medium]"
|
||||
),
|
||||
@@ -67,11 +69,48 @@ def main(
|
||||
),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="Подробный вывод"),
|
||||
force: bool = typer.Option(False, "--force", "-f", help="Перезаписать существующие транскрипты"),
|
||||
install_menu: bool = typer.Option(False, "--install-menu", help="Установить пункт Transcribe в SendTo"),
|
||||
uninstall_menu: bool = typer.Option(False, "--uninstall-menu", help="Удалить пункт Transcribe из SendTo"),
|
||||
) -> None:
|
||||
"""Транскрибирует аудио/видеофайлы в markdown с таймкодами.
|
||||
|
||||
Каскад приоритетов параметров: CLI-флаги > .transcriber.toml > device-aware дефолты.
|
||||
"""
|
||||
files = [] if files is None else files
|
||||
|
||||
if install_menu or uninstall_menu:
|
||||
if install_menu and uninstall_menu:
|
||||
console.print("--install-menu и --uninstall-menu несовместимы.", style="red bold")
|
||||
raise SystemExit(2)
|
||||
if files:
|
||||
console.print("Флаги меню нельзя использовать вместе с файлами.", style="red bold")
|
||||
raise SystemExit(2)
|
||||
if sys.platform != "win32":
|
||||
console.print("Пункт меню SendTo доступен только на Windows.", style="red bold")
|
||||
raise SystemExit(1)
|
||||
|
||||
try:
|
||||
if install_menu:
|
||||
cmd_path = install_context_menu()
|
||||
console.print(f"Пункт меню установлен: \"{cmd_path}\"", style="green")
|
||||
else:
|
||||
cmd_path = uninstall_context_menu()
|
||||
if cmd_path is None:
|
||||
console.print("Пункт меню не был установлен.", style="yellow")
|
||||
else:
|
||||
console.print(f"Пункт меню удалён: \"{cmd_path}\"", style="green")
|
||||
except RuntimeError as exc:
|
||||
console.print(f"Ошибка: {exc}", style="red bold")
|
||||
raise SystemExit(1)
|
||||
return
|
||||
|
||||
if not files:
|
||||
console.print(
|
||||
"Укажите хотя бы один файл или используйте --install-menu/--uninstall-menu.",
|
||||
style="red bold",
|
||||
)
|
||||
raise SystemExit(2)
|
||||
|
||||
try:
|
||||
config = load_config()
|
||||
cli_values = {"model": model, "language": language, "device": device, "compute_type": compute_type}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Установка пункта Transcribe в меню SendTo проводника Windows."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CMD_NAME = "Transcribe.cmd"
|
||||
CMD_ENCODING = "oem"
|
||||
|
||||
|
||||
def get_sendto_dir() -> Path:
|
||||
"""Возвращает путь к пользовательской папке SendTo."""
|
||||
appdata = os.environ.get("APPDATA")
|
||||
if appdata is None:
|
||||
raise RuntimeError("Переменная окружения APPDATA не задана.")
|
||||
return Path(appdata) / "Microsoft" / "Windows" / "SendTo"
|
||||
|
||||
|
||||
def get_transcribe_exe() -> Path:
|
||||
"""Возвращает путь к transcribe.exe рядом с текущим интерпретатором."""
|
||||
transcribe_exe = Path(sys.executable).parent / "transcribe.exe"
|
||||
if not transcribe_exe.exists():
|
||||
raise RuntimeError(
|
||||
f"Не найден transcribe.exe рядом с Python: {transcribe_exe}. "
|
||||
"Выполните uv sync и повторите установку пункта меню."
|
||||
)
|
||||
return transcribe_exe
|
||||
|
||||
|
||||
def install_menu() -> Path:
|
||||
"""Создаёт или обновляет Transcribe.cmd в папке SendTo."""
|
||||
sendto_dir = get_sendto_dir()
|
||||
transcribe_exe = get_transcribe_exe()
|
||||
cmd_path = sendto_dir / CMD_NAME
|
||||
content = f'@echo off\r\n"{transcribe_exe}" %*\r\npause\r\n'
|
||||
|
||||
try:
|
||||
encoded_content = content.encode(CMD_ENCODING)
|
||||
except UnicodeEncodeError as exc:
|
||||
raise RuntimeError(
|
||||
"Путь к transcribe.exe содержит символы, которые нельзя записать "
|
||||
"в OEM-кодировке cmd.exe. Установите проект в путь без таких символов "
|
||||
"и повторите --install-menu."
|
||||
) from exc
|
||||
|
||||
sendto_dir.mkdir(parents=True, exist_ok=True)
|
||||
cmd_path.write_bytes(encoded_content)
|
||||
return cmd_path
|
||||
|
||||
|
||||
def uninstall_menu() -> Path | None:
|
||||
"""Удаляет Transcribe.cmd из папки SendTo, если он существует."""
|
||||
cmd_path = get_sendto_dir() / CMD_NAME
|
||||
if not cmd_path.exists():
|
||||
return None
|
||||
cmd_path.unlink()
|
||||
return cmd_path
|
||||
Reference in New Issue
Block a user