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.
- speakvora-0.3.0/PKG-INFO +26 -0
- speakvora-0.3.0/README.md +13 -0
- speakvora-0.3.0/pyproject.toml +22 -0
- speakvora-0.3.0/setup.cfg +4 -0
- speakvora-0.3.0/speakvora/__init__.py +130 -0
- speakvora-0.3.0/speakvora.egg-info/PKG-INFO +26 -0
- speakvora-0.3.0/speakvora.egg-info/SOURCES.txt +8 -0
- speakvora-0.3.0/speakvora.egg-info/dependency_links.txt +1 -0
- speakvora-0.3.0/speakvora.egg-info/requires.txt +3 -0
- speakvora-0.3.0/speakvora.egg-info/top_level.txt +1 -0
speakvora-0.3.0/PKG-INFO
ADDED
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
speakvora
|