Add /api/capture and /api/captures endpoints — auth-free, WebDAV delivery to Journal/Captures/

This commit is contained in:
2026-04-26 22:39:55 +00:00
parent 8c8fba11b8
commit a07de922df
+90
View File
@@ -625,6 +625,96 @@ async def transcribe_audio(request: Request, audio: UploadFile = File(...), auth
os.unlink(tmp_path)
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/capture")
async def capture_audio(audio: UploadFile = File(...)):
"""Auth-free capture endpoint — saves transcribed audio to Nextcloud Journal/Captures/"""
if not whisper_model:
raise HTTPException(status_code=503, detail="Whisper not available")
tmp_path = None
try:
suffix = ".webm"
if audio.content_type and "mp4" in audio.content_type:
suffix = ".mp4"
elif audio.content_type and "ogg" in audio.content_type:
suffix = ".ogg"
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
content = await audio.read()
tmp.write(content)
tmp_path = tmp.name
segments, info = whisper_model.transcribe(
tmp_path,
language="en",
vad_filter=True,
initial_prompt=WHISPER_PROMPT
)
transcript = " ".join(s.text.strip() for s in segments).strip()
os.unlink(tmp_path)
tmp_path = None
if not transcript:
return JSONResponse({"ok": False, "error": "No speech detected"})
# Save to Nextcloud Journal/Captures/ via WebDAV
import requests as req_lib
from datetime import datetime
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M")
filename = f"{timestamp}-voice.md"
content_md = f"# Capture — {timestamp}\n\n**type:** voice\n**modality:** audio\n**status:** unprocessed\n\n---\n\n{transcript}\n"
nextcloud_url = os.getenv("NEXTCLOUD_URL", "")
nextcloud_user = os.getenv("NEXTCLOUD_USER", "aaron")
nextcloud_password = os.getenv("NEXTCLOUD_PASSWORD", "")
captures_dir = f"{nextcloud_url}/remote.php/dav/files/{nextcloud_user}/Journal/Captures"
auth = (nextcloud_user, nextcloud_password)
req_lib.request("MKCOL", captures_dir, auth=auth, timeout=10)
url = f"{captures_dir}/{filename}"
response = req_lib.put(url, data=content_md.encode("utf-8"), auth=auth, timeout=30)
response.raise_for_status()
return JSONResponse({"ok": True, "filename": filename, "transcript": transcript})
except Exception as e:
if tmp_path and os.path.exists(tmp_path):
os.unlink(tmp_path)
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/captures")
async def list_captures():
"""Returns recent captures from Nextcloud Journal/Captures/ — auth-free"""
try:
import requests as req_lib
nextcloud_url = os.getenv("NEXTCLOUD_URL", "")
nextcloud_user = os.getenv("NEXTCLOUD_USER", "aaron")
nextcloud_password = os.getenv("NEXTCLOUD_PASSWORD", "")
captures_dir = f"{nextcloud_url}/remote.php/dav/files/{nextcloud_user}/Journal/Captures"
auth = (nextcloud_user, nextcloud_password)
propfind = req_lib.request("PROPFIND", captures_dir, auth=auth, timeout=10,
headers={"Depth": "1"})
if propfind.status_code == 404:
return JSONResponse({"captures": []})
import xml.etree.ElementTree as ET
root = ET.fromstring(propfind.text)
ns = {"d": "DAV:"}
captures = []
for resp in root.findall("d:response", ns):
href = resp.findtext("d:href", namespaces=ns) or ""
if href.endswith("/"):
continue
name = href.split("/")[-1]
if not name.endswith(".md"):
continue
captures.append({"name": name.replace(".md", ""), "duration": ""})
captures.sort(key=lambda x: x["name"], reverse=True)
return JSONResponse({"captures": captures[:10]})
except Exception as e:
return JSONResponse({"captures": []})
@app.post("/api/reindex")
async def trigger_reindex(auth: str = Depends(require_auth)):
try: