mirror of
https://github.com/Luchexx/claude-video.git
synced 2026-11-02 09:19:48 +00:00
feat: setup wizard engine choice and Gemini-aware readiness
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
6dcc18a859
commit
afbc483356
@ -12,8 +12,8 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from config import (CONFIG_DIR, CONFIG_FILE, ConfigError, DETAILS, get_config,
|
from config import (CONFIG_DIR, CONFIG_FILE, ConfigError, DETAILS, ENGINES, get_config,
|
||||||
load_api_key, read_env_file, write_settings)
|
load_api_key, load_gemini_key, read_env_file, resolve_engine, write_settings)
|
||||||
from runtime import configure_stdio, diagnostic, run_text
|
from runtime import configure_stdio, diagnostic, run_text
|
||||||
|
|
||||||
REQUIRED_BINARIES = ['ffmpeg', 'ffprobe', 'yt-dlp']
|
REQUIRED_BINARIES = ['ffmpeg', 'ffprobe', 'yt-dlp']
|
||||||
@ -23,6 +23,9 @@ ENV_TEMPLATE = '''# /watch configuration. No shell interpolation; last assignmen
|
|||||||
# Native captions always come first. Optional fallback: auto|whisperx|groq|openai|none.
|
# Native captions always come first. Optional fallback: auto|whisperx|groq|openai|none.
|
||||||
# auto preserves the Groq-then-OpenAI preference for existing installations.
|
# auto preserves the Groq-then-OpenAI preference for existing installations.
|
||||||
# The first-run skill wizard sets WATCH_DETAIL and WATCH_WHISPER_BACKEND.
|
# The first-run skill wizard sets WATCH_DETAIL and WATCH_WHISPER_BACKEND.
|
||||||
|
# WATCH_ENGINE=auto|gemini|local. auto uses Gemini when GEMINI_API_KEY is set.
|
||||||
|
# With the gemini engine, local videos are uploaded to Google for analysis.
|
||||||
|
GEMINI_API_KEY=
|
||||||
GROQ_API_KEY=
|
GROQ_API_KEY=
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
'''
|
'''
|
||||||
@ -142,12 +145,21 @@ def _status(detailed=False):
|
|||||||
missing = _check_binaries()
|
missing = _check_binaries()
|
||||||
_check_file_permissions(CONFIG_FILE)
|
_check_file_permissions(CONFIG_FILE)
|
||||||
cfg = get_config()
|
cfg = get_config()
|
||||||
|
gemini_key = bool(load_gemini_key())
|
||||||
|
try:
|
||||||
|
engine = resolve_engine(cfg['engine'], gemini_key)
|
||||||
|
except ConfigError:
|
||||||
|
engine = 'gemini' # Explicitly chosen but the key is missing; gemini_key_present reports it.
|
||||||
|
binaries_required = engine == 'local'
|
||||||
has_key, detected = _have_api_key()
|
has_key, detected = _have_api_key()
|
||||||
chosen = cfg['whisper_backend']
|
chosen = cfg['whisper_backend']
|
||||||
backend = detected if chosen == 'auto' else chosen
|
backend = detected if chosen == 'auto' else chosen
|
||||||
local_ready = _whisperx_status(cfg, detailed)
|
local_ready = _whisperx_status(cfg, detailed)
|
||||||
backend_ready = local_ready if chosen == 'whisperx' else (bool(load_api_key(backend)[1]) if backend in ('groq', 'openai') else chosen == 'none')
|
backend_ready = local_ready if chosen == 'whisperx' else (bool(load_api_key(backend)[1]) if backend in ('groq', 'openai') else chosen == 'none')
|
||||||
result = {'status': 'needs_install' if missing else 'ready', 'can_proceed': not missing,
|
blocked = bool(missing) and binaries_required
|
||||||
|
result = {'status': 'needs_install' if blocked else 'ready', 'can_proceed': not blocked,
|
||||||
|
'engine': engine, 'configured_engine': cfg['engine'], 'gemini_key_present': gemini_key,
|
||||||
|
'gemini_model': cfg['gemini_model'], 'binaries_required': binaries_required,
|
||||||
'first_run': is_first_run(), 'setup_complete': not is_first_run(),
|
'first_run': is_first_run(), 'setup_complete': not is_first_run(),
|
||||||
'missing_binaries': missing, 'whisper_backend': backend, 'configured_backend': chosen,
|
'missing_binaries': missing, 'whisper_backend': backend, 'configured_backend': chosen,
|
||||||
'has_api_key': has_key, 'backend_ready': backend_ready,
|
'has_api_key': has_key, 'backend_ready': backend_ready,
|
||||||
@ -184,7 +196,20 @@ def cmd_json():
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def cmd_install(backend=None, detail=None):
|
def cmd_install(backend=None, detail=None, engine=None):
|
||||||
|
if engine:
|
||||||
|
_scaffold_env()
|
||||||
|
write_settings({'WATCH_ENGINE': engine}, CONFIG_FILE)
|
||||||
|
if engine == 'gemini':
|
||||||
|
if not load_gemini_key():
|
||||||
|
print(f'[setup] Add GEMINI_API_KEY privately to {CONFIG_FILE} (free key: https://aistudio.google.com/apikey), '
|
||||||
|
'then rerun --engine gemini. Local videos will be uploaded to Google for analysis.', file=sys.stderr)
|
||||||
|
return 3
|
||||||
|
_write_setup_complete()
|
||||||
|
missing = _check_binaries()
|
||||||
|
note = (f" Optional: install {', '.join(_brew_pkg(missing))} for non-YouTube URLs and --engine local." if missing else '')
|
||||||
|
print(f'[setup] Gemini engine is ready. Configuration: {CONFIG_FILE}.{note}')
|
||||||
|
return 0
|
||||||
missing = _check_binaries()
|
missing = _check_binaries()
|
||||||
if missing:
|
if missing:
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
@ -295,13 +320,14 @@ def main():
|
|||||||
mode.add_argument('--install-whisperx', action='store_true')
|
mode.add_argument('--install-whisperx', action='store_true')
|
||||||
parser.add_argument('--backend', choices=['auto', 'whisperx', 'groq', 'openai', 'none'])
|
parser.add_argument('--backend', choices=['auto', 'whisperx', 'groq', 'openai', 'none'])
|
||||||
parser.add_argument('--detail', choices=sorted(DETAILS))
|
parser.add_argument('--detail', choices=sorted(DETAILS))
|
||||||
|
parser.add_argument('--engine', choices=sorted(ENGINES))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
try:
|
try:
|
||||||
if args.check:
|
if args.check:
|
||||||
return cmd_check()
|
return cmd_check()
|
||||||
if args.json:
|
if args.json:
|
||||||
return cmd_json()
|
return cmd_json()
|
||||||
return cmd_install('whisperx' if args.install_whisperx else args.backend, args.detail)
|
return cmd_install('whisperx' if args.install_whisperx else args.backend, args.detail, args.engine)
|
||||||
except (ConfigError, OSError, ValueError) as exc:
|
except (ConfigError, OSError, ValueError) as exc:
|
||||||
print(f'[setup] {exc}', file=sys.stderr)
|
print(f'[setup] {exc}', file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
|
|||||||
@ -105,3 +105,43 @@ def test_none_backend_completion():
|
|||||||
assert not setup.is_first_run()
|
assert not setup.is_first_run()
|
||||||
assert config.get_config()['whisper_backend'] == 'none'
|
assert config.get_config()['whisper_backend'] == 'none'
|
||||||
assert config.get_config()['detail'] == 'efficient'
|
assert config.get_config()['detail'] == 'efficient'
|
||||||
|
|
||||||
|
|
||||||
|
def test_gemini_engine_is_ready_without_media_binaries(monkeypatch):
|
||||||
|
monkeypatch.setenv('GEMINI_API_KEY', 'unit-key')
|
||||||
|
monkeypatch.setattr(setup, '_check_binaries', lambda: list(setup.REQUIRED_BINARIES))
|
||||||
|
status = setup._status()
|
||||||
|
assert status['engine'] == 'gemini' and status['configured_engine'] == 'auto'
|
||||||
|
assert status['gemini_key_present'] is True and status['gemini_model'] == 'gemini-3.7-flash'
|
||||||
|
assert status['can_proceed'] is True and status['status'] == 'ready'
|
||||||
|
assert status['binaries_required'] is False
|
||||||
|
assert status['missing_binaries'] == ['ffmpeg', 'ffprobe', 'yt-dlp']
|
||||||
|
assert 'unit-key' not in json.dumps(status)
|
||||||
|
assert setup.cmd_check() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_engine_still_requires_binaries(monkeypatch):
|
||||||
|
monkeypatch.setattr(setup, '_check_binaries', lambda: list(setup.REQUIRED_BINARIES))
|
||||||
|
status = setup._status()
|
||||||
|
assert status['engine'] == 'local' and status['gemini_key_present'] is False
|
||||||
|
assert status['can_proceed'] is False and status['binaries_required'] is True
|
||||||
|
assert setup.cmd_check() == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_engine_gemini_without_key_writes_choice_and_asks_for_key(monkeypatch, capsys):
|
||||||
|
monkeypatch.setattr(setup, '_check_binaries', lambda: list(setup.REQUIRED_BINARIES))
|
||||||
|
assert setup.cmd_install(engine='gemini') == 3
|
||||||
|
assert 'GEMINI_API_KEY' in capsys.readouterr().err
|
||||||
|
values = config.read_env_file(config.CONFIG_FILE)
|
||||||
|
assert values['WATCH_ENGINE'] == 'gemini' and values.get('SETUP_COMPLETE') != 'true'
|
||||||
|
|
||||||
|
|
||||||
|
def test_engine_gemini_with_key_completes_setup_and_preserves_lines(monkeypatch):
|
||||||
|
monkeypatch.setattr(setup, '_check_binaries', lambda: list(setup.REQUIRED_BINARIES))
|
||||||
|
config.CONFIG_DIR.mkdir(parents=True)
|
||||||
|
config.CONFIG_FILE.write_text('# my notes\nGEMINI_API_KEY=unit-key\nGROQ_API_KEY=keep-me\n')
|
||||||
|
assert setup.cmd_install(engine='gemini') == 0
|
||||||
|
text = config.CONFIG_FILE.read_text()
|
||||||
|
assert '# my notes' in text and 'GROQ_API_KEY=keep-me' in text
|
||||||
|
values = config.read_env_file(config.CONFIG_FILE)
|
||||||
|
assert values['WATCH_ENGINE'] == 'gemini' and values['SETUP_COMPLETE'] == 'true'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user