Add /api/capture and /api/captures endpoints — auth-free, WebDAV delivery to Journal/Captures/
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user