requests package.
pip install requests
export AIGENDA_EMAIL="you@example.com"
export AIGENDA_PASSWORD="your-password"
python aigenda_upload.py "Sales team standup.mp3" audio/mpeg
aigenda_upload.py
"""Upload a recording to AiGenda and get the protocol.
Usage: python aigenda_upload.py <file> <content_type>
"""
import math
import os
import sys
import time
import requests
API = "https://api.aigenda.tech"
MIN_RESERVE_SECONDS = 5 * 60 # don't start if almost no minutes are left
def login(email: str, password: str) -> str:
for attempt in range(3):
r = requests.post(f"{API}/api/token/", json={"username": email, "password": password}, timeout=30)
if r.status_code == 401:
raise SystemExit("Wrong email or password")
if r.ok and r.headers.get("Content-Type", "").startswith("application/json"):
return r.json()["access"]
time.sleep(1.5)
r.raise_for_status()
raise RuntimeError("Could not sign in")
def main_space(headers: dict) -> dict:
r = requests.get(f"{API}/api/spaces/", params={"limit": 50, "offset": 0}, headers=headers, timeout=30)
r.raise_for_status()
return next(item["space"] for item in r.json()["results"] if item["is_main_space"])
def available_seconds(headers: dict, space_id: int) -> int:
r = requests.get(f"{API}/api/spaces/{space_id}/", headers=headers, timeout=30)
r.raise_for_status()
return r.json()["subscribe"]["available_usage_count"]
def upload_file(headers: dict, space_id: int, path: str, content_type: str) -> int:
size = os.path.getsize(path)
r = requests.post(
f"{API}/api/spaces/{space_id}/audio/",
headers=headers,
json={"title": os.path.basename(path), "size": size, "content_type": content_type},
timeout=30,
)
r.raise_for_status()
audio = r.json()
part_size = math.ceil(size / len(audio["url"]))
parts = []
with open(path, "rb") as f:
for number, url in enumerate(audio["url"], start=1):
put = requests.put(url, data=f.read(part_size), timeout=900)
put.raise_for_status()
parts.append({"PartNumber": number, "ETag": put.headers["ETag"]})
r = requests.post(
f"{API}/api/spaces/{space_id}/audio/{audio['id']}/complete_upload/",
headers=headers,
json={"parts": parts},
timeout=30,
)
r.raise_for_status()
for _ in range(60):
info = requests.get(f"{API}/api/spaces/{space_id}/audio/{audio['id']}/info/", headers=headers, timeout=30)
if info.ok and info.json().get("is_uploaded"):
return audio["id"]
time.sleep(2)
raise TimeoutError("AiGenda did not confirm the file within 2 minutes")
def start_processing(headers: dict, audio_id: int) -> None:
r = requests.post(f"{API}/api/audio/classify/", headers=headers, json={"audio_id": audio_id}, timeout=30)
r.raise_for_status()
def wait_for_result(headers: dict, space_id: int, audio_id: int, timeout: int = 3600) -> dict:
url = f"{API}/api/spaces/{space_id}/audio/{audio_id}/result/"
started = time.monotonic()
not_found_since = failing_since = None
while time.monotonic() - started < timeout:
r = requests.get(url, headers=headers, timeout=30)
now = time.monotonic()
if r.status_code == 200:
not_found_since = failing_since = None
body = r.json()
if body.get("classified"):
if body.get("success"):
return body
raise RuntimeError("Processing failed, the minutes will be returned automatically")
elif r.status_code == 404:
not_found_since = not_found_since or now
if now - not_found_since > 60:
raise RuntimeError("Processing not started")
elif r.status_code >= 500:
failing_since = failing_since or now
if now - failing_since > 180:
raise RuntimeError("Processing failed, the minutes will be returned automatically")
time.sleep(15)
raise TimeoutError("The result was not ready within an hour")
def main() -> None:
path, content_type = sys.argv[1], sys.argv[2]
token = login(os.environ["AIGENDA_EMAIL"], os.environ["AIGENDA_PASSWORD"])
headers = {"Authorization": f"Bearer {token}"}
space = main_space(headers)
left = available_seconds(headers, space["id"])
print(f"Space \"{space['name']}\", {left // 60} min left")
if left <= MIN_RESERVE_SECONDS:
raise SystemExit("Almost no minutes left on the plan")
audio_id = upload_file(headers, space["id"], path, content_type)
print(f"File uploaded, audio_id={audio_id}")
start_processing(headers, audio_id)
print("Processing started, waiting for the result…")
result = wait_for_result(headers, space["id"], audio_id)
out = os.path.splitext(path)[0] + ".protocol.md"
with open(out, "w", encoding="utf-8") as f:
f.write(result["result"]["protocol"])
print(f"Protocol saved: {out}")
if __name__ == "__main__":
main()