speakvora 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: speakvora
3
+ Version: 0.3.0
4
+ Summary: Speakvora SDK: text to speech, speech to text, phrase library, offline packs, voice agents and live streaming.
5
+ License: MIT
6
+ Project-URL: Homepage, https://speakvora.com/docs
7
+ Project-URL: Repository, https://github.com/eyeguide-dev/speakvora-sdk
8
+ Keywords: text-to-speech,tts,speech-to-text,voice,ivr,voice-agent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Provides-Extra: stream
12
+ Requires-Dist: websockets; extra == "stream"
13
+
14
+ # speakvora (Python)
15
+
16
+ ```python
17
+ from speakvora import Speakvora
18
+ sv = Speakvora(os.environ["SPEAKVORA_KEY"]) # get a key at https://speakvora.com/dashboard
19
+ clip = sv.speak("Your order has shipped.", voice="af_heart") # {"url": ..., "format": ...}
20
+ sv.prepare(["Your table is ready."]) # make known lines instant
21
+ text = sv.listen(open("q.wav", "rb").read(), format="wav")["text"]
22
+ turn = sv.agent("ag_123", agent_key).turn(session="call-1", text="What time do you close?")
23
+ ```
24
+
25
+ Errors are `SpeakvoraError` with `status`, `code` and a plain-language `fix`. Live streaming: `pip install "speakvora[stream]"`.
26
+ Full reference: https://speakvora.com/docs
@@ -0,0 +1,13 @@
1
+ # speakvora (Python)
2
+
3
+ ```python
4
+ from speakvora import Speakvora
5
+ sv = Speakvora(os.environ["SPEAKVORA_KEY"]) # get a key at https://speakvora.com/dashboard
6
+ clip = sv.speak("Your order has shipped.", voice="af_heart") # {"url": ..., "format": ...}
7
+ sv.prepare(["Your table is ready."]) # make known lines instant
8
+ text = sv.listen(open("q.wav", "rb").read(), format="wav")["text"]
9
+ turn = sv.agent("ag_123", agent_key).turn(session="call-1", text="What time do you close?")
10
+ ```
11
+
12
+ Errors are `SpeakvoraError` with `status`, `code` and a plain-language `fix`. Live streaming: `pip install "speakvora[stream]"`.
13
+ Full reference: https://speakvora.com/docs
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "speakvora"
7
+ version = "0.3.0"
8
+ description = "Speakvora SDK: text to speech, speech to text, phrase library, offline packs, voice agents and live streaming."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ keywords = ["text-to-speech", "tts", "speech-to-text", "voice", "ivr", "voice-agent"]
13
+
14
+ [project.optional-dependencies]
15
+ stream = ["websockets"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://speakvora.com/docs"
19
+ Repository = "https://github.com/eyeguide-dev/speakvora-sdk"
20
+
21
+ [tool.setuptools]
22
+ packages = ["speakvora"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,130 @@
1
+ """Speakvora Python SDK 0.3.0: text to speech, speech to text, phrase library, offline packs, voice agents and live streaming.
2
+ Standard library only (live streaming needs the optional `websockets` package). Keep your API key on a server.
3
+
4
+ from speakvora import Speakvora
5
+ sv = Speakvora(os.environ["SPEAKVORA_KEY"])
6
+ clip = sv.speak("Your order has shipped.", voice="af_heart") # {"url": ..., "format": ..., "lane": ...}
7
+ sv.prepare(["Your table is ready.", "Your order is on the way."]) # make known lines instant
8
+ text = sv.listen(open("q.wav", "rb").read(), format="wav")["text"] # speech to text
9
+ reply = sv.agent("ag_123", agent_key).turn(session="call-1", text="What time do you close?")
10
+ """
11
+ import base64, hashlib, hmac, json, time, urllib.error, urllib.parse, urllib.request
12
+
13
+ __version__ = "0.3.0"
14
+ DEFAULT_BASE = "https://speakvora.com/api"
15
+
16
+
17
+ class SpeakvoraError(Exception):
18
+ def __init__(self, status, body):
19
+ body = body if isinstance(body, dict) else {}
20
+ super().__init__(body.get("message") or body.get("error") or f"http_{status}")
21
+ self.status, self.code, self.fix, self.body = status, body.get("error"), body.get("fix"), body
22
+
23
+
24
+ def _b64(data):
25
+ return data if isinstance(data, str) else base64.b64encode(bytes(data)).decode()
26
+
27
+
28
+ class Agent:
29
+ def __init__(self, sv, agent_id, key):
30
+ self._sv, self._p, self._k = sv, f"/v1/agents/{urllib.parse.quote(agent_id, safe='')}", key
31
+
32
+ def turn(self, session=None, text=None, audio=None, start=False, interrupted=None):
33
+ body = {"session": session, **({"text": text} if text else {}), **({"audio_b64": _b64(audio)} if audio else {}), **({"start": True} if start else {}), **({"interrupted": interrupted} if interrupted else {})}
34
+ return self._sv._req(self._p + "/turn", body, key=self._k)
35
+
36
+ def call(self, to):
37
+ """The agent phones this number (testing; five a day)."""
38
+ return self._sv._req(self._p + "/call", {"to": to}, key=self._k)
39
+
40
+ def inbound(self):
41
+ """Web addresses to put on your own Twilio number so people can call and text the agent: {"voice_url", "sms_url", "how"}."""
42
+ return self._sv._req(self._p + "/inbound", {}, key=self._k)
43
+
44
+ def sessions(self):
45
+ return self._sv._req(self._p + "/sessions", {}, key=self._k) # mints a 10-minute browser token
46
+
47
+ def interrupt(self, session, generation, played):
48
+ return self._sv._req(self._p + "/interrupt", {"session": session, "generation": generation, "played": played}, key=self._k)
49
+
50
+
51
+ class Speakvora:
52
+ def __init__(self, key, base=DEFAULT_BASE, timeout=60):
53
+ if not key:
54
+ raise ValueError("Speakvora: API key required")
55
+ self.key, self.base, self.timeout = key, base.rstrip("/"), timeout
56
+
57
+ def _req(self, path, data=None, key=None):
58
+ req = urllib.request.Request(self.base + path, data=json.dumps(data).encode() if data is not None else None, method="POST" if data is not None else "GET",
59
+ headers={"content-type": "application/json", "x-api-key": key or self.key, "user-agent": f"speakvora-python/{__version__}"})
60
+ try:
61
+ with urllib.request.urlopen(req, timeout=self.timeout) as r:
62
+ return json.loads(r.read() or b"{}")
63
+ except urllib.error.HTTPError as e:
64
+ try:
65
+ body = json.loads(e.read() or b"{}")
66
+ except ValueError:
67
+ body = {}
68
+ raise SpeakvoraError(e.code, body)
69
+
70
+ # speech
71
+ def speak(self, text, voice="af_heart", lang="en", style=None, fast=False, marks=False):
72
+ """marks=True also returns speech marks (exact sentence times, estimated word times) for live-generated text. voice: a catalog id (af_heart...) or your own voice (cv_...). style (own voices, English): happy|excited|sorry|calm|serious."""
73
+ return self._req("/v1/speak", {"text": text, "voice": voice, "lang": lang, **({"style": style} if style else {}), **({"fast": True} if fast else {}), **({"marks": True} if marks else {})})
74
+
75
+ def prepare(self, texts, voice="af_heart", lang="en"):
76
+ """Get up to 200 lines ready ahead of time so speak() returns them quickly. Not billed."""
77
+ return self._req("/v1/prepare", {"texts": list(texts), "voice": voice, "lang": lang})
78
+
79
+ def save(self, text, path, **kw):
80
+ clip = self.speak(text, **kw)
81
+ with urllib.request.urlopen(clip["url"], timeout=self.timeout) as r, open(path, "wb") as f:
82
+ f.write(r.read())
83
+ return clip
84
+
85
+ # speech to text
86
+ def listen(self, audio, format="wav", language=None):
87
+ """audio: bytes or a base64 string; 1 KB to 4.5 MB per call."""
88
+ return self._req("/v1/listen", {"audio_base64": _b64(audio), "format": format, **({"language": language} if language else {})})
89
+
90
+ # library
91
+ def voices(self): return self._req("/v1/voices")
92
+ def languages(self): return self._req("/v1/languages")
93
+ def search(self, q, lang="en", limit=10): return self._req(f"/v1/search?q={urllib.parse.quote(q)}&lang={lang}&limit={limit}")
94
+ def library_stats(self): return self._req("/library/stats")
95
+
96
+ # offline packs
97
+ def packs(self): return self._req("/v1/packs")
98
+ def pack_download(self, pack_id):
99
+ """After the pack is bought on your account: {"url": ...} to a zip, valid 5 minutes. Raises SpeakvoraError(402, code pack_not_purchased) otherwise."""
100
+ return self._req(f"/v1/packs/{urllib.parse.quote(pack_id, safe='')}/download")
101
+
102
+ # agents
103
+ def agent(self, agent_id, agent_key=None):
104
+ return Agent(self, agent_id, agent_key or self.key)
105
+
106
+ # live streaming (optional dependency: pip install websockets)
107
+ async def stream_speak(self, ws_url, text, voice="af_heart", lang="en"):
108
+ """Async generator: yields {"type":"audio","url":...} for each sentence as it is ready, then stops at "done"."""
109
+ import websockets
110
+ async with websockets.connect(ws_url) as ws:
111
+ await ws.send(json.dumps({"type": "auth", "key": self.key})); first = json.loads(await ws.recv())
112
+ if first.get("type") != "ready":
113
+ raise SpeakvoraError(401, first)
114
+ await ws.send(json.dumps({"type": "speak", "id": "1", "text": text, "voice": voice, "lang": lang}))
115
+ while True:
116
+ m = json.loads(await ws.recv())
117
+ if m["type"] == "error":
118
+ raise SpeakvoraError(400, m)
119
+ yield m
120
+ if m["type"] == "done":
121
+ return
122
+
123
+
124
+ def verify_webhook(secret, raw_body, headers, tolerance=300):
125
+ h = {k.lower(): v for k, v in headers.items()}
126
+ ts, sig = h.get("x-speakvora-timestamp"), h.get("x-speakvora-signature")
127
+ if not ts or not sig or abs(time.time() - int(ts)) > tolerance:
128
+ return False
129
+ body = raw_body if isinstance(raw_body, bytes) else raw_body.encode()
130
+ return hmac.compare_digest(hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest(), sig)
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: speakvora
3
+ Version: 0.3.0
4
+ Summary: Speakvora SDK: text to speech, speech to text, phrase library, offline packs, voice agents and live streaming.
5
+ License: MIT
6
+ Project-URL: Homepage, https://speakvora.com/docs
7
+ Project-URL: Repository, https://github.com/eyeguide-dev/speakvora-sdk
8
+ Keywords: text-to-speech,tts,speech-to-text,voice,ivr,voice-agent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Provides-Extra: stream
12
+ Requires-Dist: websockets; extra == "stream"
13
+
14
+ # speakvora (Python)
15
+
16
+ ```python
17
+ from speakvora import Speakvora
18
+ sv = Speakvora(os.environ["SPEAKVORA_KEY"]) # get a key at https://speakvora.com/dashboard
19
+ clip = sv.speak("Your order has shipped.", voice="af_heart") # {"url": ..., "format": ...}
20
+ sv.prepare(["Your table is ready."]) # make known lines instant
21
+ text = sv.listen(open("q.wav", "rb").read(), format="wav")["text"]
22
+ turn = sv.agent("ag_123", agent_key).turn(session="call-1", text="What time do you close?")
23
+ ```
24
+
25
+ Errors are `SpeakvoraError` with `status`, `code` and a plain-language `fix`. Live streaming: `pip install "speakvora[stream]"`.
26
+ Full reference: https://speakvora.com/docs
@@ -0,0 +1,8 @@
1
+ README.md
2
+ pyproject.toml
3
+ speakvora/__init__.py
4
+ speakvora.egg-info/PKG-INFO
5
+ speakvora.egg-info/SOURCES.txt
6
+ speakvora.egg-info/dependency_links.txt
7
+ speakvora.egg-info/requires.txt
8
+ speakvora.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+
2
+ [stream]
3
+ websockets
@@ -0,0 +1 @@
1
+ speakvora