mirror of
https://github.com/Luchexx/claude-video.git
synced 2026-11-02 09:19:48 +00:00
fix: Gemini acceptance findings — auth on HTTP 400, honest source line, work-dir cleanup
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
bb1738b7bd
commit
07a1560b6e
@ -68,9 +68,11 @@ def _call(method: str, url: str, key: str, *, data=None, headers=None, timeout:
|
|||||||
with urlopen(request, timeout=timeout) as response:
|
with urlopen(request, timeout=timeout) as response:
|
||||||
return response.status, dict(response.headers), response.read()
|
return response.status, dict(response.headers), response.read()
|
||||||
except HTTPError as exc:
|
except HTTPError as exc:
|
||||||
category = ('auth' if exc.code in (401, 403) else 'quota' if exc.code == 429
|
message = _error_message(exc.read())
|
||||||
|
# The Files API answers a malformed key with 400 "API key not valid", not 401.
|
||||||
|
category = ('auth' if exc.code in (401, 403) or 'api key' in message.lower() else 'quota' if exc.code == 429
|
||||||
else 'service' if exc.code >= 500 else 'rejected')
|
else 'service' if exc.code >= 500 else 'rejected')
|
||||||
raise _fail(category, f'HTTP {exc.code}: {_error_message(exc.read())}', key) from None
|
raise _fail(category, f'HTTP {exc.code}: {message}', key) from None
|
||||||
except (URLError, TimeoutError, OSError) as exc:
|
except (URLError, TimeoutError, OSError) as exc:
|
||||||
raise _fail('network', f'{type(exc).__name__}: {getattr(exc, "reason", exc)}', key) from None
|
raise _fail('network', f'{type(exc).__name__}: {getattr(exc, "reason", exc)}', key) from None
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import math
|
import math
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -34,7 +35,7 @@ def run_gemini(args, config, key, start_sec, end_sec, auth) -> int:
|
|||||||
ignored = [flag for flag, name in LOCAL_ONLY_FLAGS if getattr(args, name) is not None]
|
ignored = [flag for flag, name in LOCAL_ONLY_FLAGS if getattr(args, name) is not None]
|
||||||
ignored += [flag for flag, on in (("--no-whisper", args.no_whisper), ("--no-dedup", args.no_dedup)) if on]
|
ignored += [flag for flag, on in (("--no-whisper", args.no_whisper), ("--no-dedup", args.no_dedup)) if on]
|
||||||
clip = (start_sec, end_sec) if start_sec is not None or end_sec is not None else None
|
clip = (start_sec, end_sec) if start_sec is not None or end_sec is not None else None
|
||||||
uploaded, warning, result, error, sent = None, None, None, None, "URL sent to Google"
|
uploaded, warning, result, error, sent, work = None, None, None, None, "URL sent to Google", None
|
||||||
try:
|
try:
|
||||||
if gemini.is_youtube(args.source):
|
if gemini.is_youtube(args.source):
|
||||||
video = {"uri": args.source}
|
video = {"uri": args.source}
|
||||||
@ -42,9 +43,11 @@ def run_gemini(args, config, key, start_sec, end_sec, auth) -> int:
|
|||||||
parent = Path(args.out_dir).expanduser().resolve() if args.out_dir else None
|
parent = Path(args.out_dir).expanduser().resolve() if args.out_dir else None
|
||||||
if parent:
|
if parent:
|
||||||
parent.mkdir(parents=True, exist_ok=True)
|
parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
sent = "not sent to Google"
|
||||||
work = Path(tempfile.mkdtemp(prefix="watch-", dir=parent))
|
work = Path(tempfile.mkdtemp(prefix="watch-", dir=parent))
|
||||||
print("[watch] downloading media…" if is_url(args.source) else "[watch] using local file…", file=sys.stderr)
|
print("[watch] downloading media…" if is_url(args.source) else "[watch] using local file…", file=sys.stderr)
|
||||||
media = download(args.source, work / "download", **(auth if is_url(args.source) else {}))
|
media = download(args.source, work / "download", **(auth if is_url(args.source) else {}))
|
||||||
|
sent = "upload to Google failed"
|
||||||
print("[watch] uploading to the Gemini Files API…", file=sys.stderr)
|
print("[watch] uploading to the Gemini Files API…", file=sys.stderr)
|
||||||
uploaded = gemini.upload_file(Path(media["video_path"]), key)
|
uploaded = gemini.upload_file(Path(media["video_path"]), key)
|
||||||
video = {"uri": uploaded["uri"], "mime_type": uploaded["mime_type"]}
|
video = {"uri": uploaded["uri"], "mime_type": uploaded["mime_type"]}
|
||||||
@ -57,6 +60,8 @@ def run_gemini(args, config, key, start_sec, end_sec, auth) -> int:
|
|||||||
finally:
|
finally:
|
||||||
if uploaded:
|
if uploaded:
|
||||||
warning = gemini.delete_file(uploaded["name"], key)
|
warning = gemini.delete_file(uploaded["name"], key)
|
||||||
|
if work: # Run-owned; holds at most a downloaded copy. A local source file lives elsewhere.
|
||||||
|
shutil.rmtree(work, ignore_errors=True)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("# watch: video report")
|
print("# watch: video report")
|
||||||
|
|||||||
@ -210,3 +210,10 @@ def test_delete_is_best_effort(calls):
|
|||||||
calls.replies = [http_error(500, b'boom')]
|
calls.replies = [http_error(500, b'boom')]
|
||||||
warning = gemini.delete_file('files/abc', KEY)
|
warning = gemini.delete_file('files/abc', KEY)
|
||||||
assert 'files/abc' in warning and '48 hours' in warning and KEY not in warning
|
assert 'files/abc' in warning and '48 hours' in warning and KEY not in warning
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_key_reported_as_http_400_is_still_auth(calls):
|
||||||
|
"""Observed live: the Files API answers a malformed key with 400, not 401."""
|
||||||
|
calls.replies = [http_error(400, b'{"error":{"code":400,"message":"API key not valid. Please pass a valid API key.","status":"INVALID_ARGUMENT"}}')]
|
||||||
|
with pytest.raises(SystemExit, match='^Gemini auth:'):
|
||||||
|
gemini.ask({'uri': 'https://youtu.be/abc'}, None, model='m', key=KEY)
|
||||||
|
|||||||
@ -269,3 +269,18 @@ def test_explicit_gemini_without_key_fails_before_any_work(monkeypatch):
|
|||||||
monkeypatch.setattr(sys, 'argv', ['watch', 'https://vimeo.com/1', '--engine', 'gemini'])
|
monkeypatch.setattr(sys, 'argv', ['watch', 'https://vimeo.com/1', '--engine', 'gemini'])
|
||||||
with pytest.raises(ConfigError, match='GEMINI_API_KEY'):
|
with pytest.raises(ConfigError, match='GEMINI_API_KEY'):
|
||||||
watch.main()
|
watch.main()
|
||||||
|
|
||||||
|
|
||||||
|
def test_failed_upload_is_reported_honestly_and_work_dir_removed(monkeypatch, capsys, static_clip, tmp_path):
|
||||||
|
import gemini
|
||||||
|
monkeypatch.setenv('GEMINI_API_KEY', 'unit-key')
|
||||||
|
def boom(path, key, **kw):
|
||||||
|
raise SystemExit('Gemini upload: nope. No local fallback was attempted; rerun with --engine local')
|
||||||
|
monkeypatch.setattr(gemini, 'upload_file', boom)
|
||||||
|
out_dir = tmp_path / 'out'
|
||||||
|
monkeypatch.setattr(sys, 'argv', ['watch', str(static_clip), '--out-dir', str(out_dir)])
|
||||||
|
assert watch.main() == 1
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert 'URL sent to Google' not in out and 'upload to Google failed' in out
|
||||||
|
assert out_dir.is_dir() and list(out_dir.iterdir()) == [], 'the run-owned work dir must be removed'
|
||||||
|
assert static_clip.is_file(), 'the user source file is never deleted'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user