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)
|
os.unlink(tmp_path)
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
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")
|
@app.post("/api/reindex")
|
||||||
async def trigger_reindex(auth: str = Depends(require_auth)):
|
async def trigger_reindex(auth: str = Depends(require_auth)):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user