oruk 0.1.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.
- oruk-0.1.0/.gitignore +34 -0
- oruk-0.1.0/PKG-INFO +88 -0
- oruk-0.1.0/README.md +61 -0
- oruk-0.1.0/pyproject.toml +47 -0
- oruk-0.1.0/src/oruk/__init__.py +3 -0
- oruk-0.1.0/src/oruk/client.py +113 -0
oruk-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# v0 sandbox internal files
|
|
2
|
+
__v0_runtime_loader.js
|
|
3
|
+
__v0_devtools.tsx
|
|
4
|
+
__v0_jsx-dev-runtime.ts
|
|
5
|
+
.snowflake/
|
|
6
|
+
.v0-trash/
|
|
7
|
+
.lazyweb/
|
|
8
|
+
|
|
9
|
+
# Environment variables
|
|
10
|
+
.env*.local
|
|
11
|
+
.dev.vars
|
|
12
|
+
|
|
13
|
+
# Social media exports (large, not part of the app)
|
|
14
|
+
twitter-gifs/
|
|
15
|
+
|
|
16
|
+
# Local browser-test scratch (screenshots, Playwright venv)
|
|
17
|
+
.demo-work/
|
|
18
|
+
|
|
19
|
+
# Common ignores
|
|
20
|
+
__pycache__/
|
|
21
|
+
node_modules
|
|
22
|
+
.next/
|
|
23
|
+
out/
|
|
24
|
+
.wrangler/
|
|
25
|
+
.open-next/
|
|
26
|
+
gha-creds-*.json
|
|
27
|
+
cloudflare-env.d.ts
|
|
28
|
+
tsconfig.tsbuildinfo
|
|
29
|
+
.DS_Store
|
|
30
|
+
hr/
|
|
31
|
+
legal/
|
|
32
|
+
|
|
33
|
+
# Confidential legal materials (separate private repo: Oruk-AI/oruk-legal)
|
|
34
|
+
legal/
|
oruk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oruk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for the oruk Speech API: English transcription, calibrated multilabel emotion and speaking-style labels, and unified audio analysis.
|
|
5
|
+
Project-URL: Homepage, https://oruk.ai
|
|
6
|
+
Project-URL: Documentation, https://oruk.ai/docs
|
|
7
|
+
Project-URL: Changelog, https://oruk.ai/changelog
|
|
8
|
+
Project-URL: Source, https://github.com/Oruk-AI/oruk-speech-lab
|
|
9
|
+
Project-URL: Pricing, https://oruk.ai/pricing
|
|
10
|
+
Author-email: oruk labs <access@oruk.ai>
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: audio analysis,emotion detection,oruk,paralinguistics,speech,speech emotion recognition,speech understanding,speech-to-text,transcription
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: httpx<1,>=0.27
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# oruk — Python client for the oruk Speech API
|
|
29
|
+
|
|
30
|
+
Official Python SDK for [oruk](https://oruk.ai), the speech lab building audio
|
|
31
|
+
models for English transcription, calibrated multilabel emotion detection,
|
|
32
|
+
speaking-style classification, and unified audio analysis.
|
|
33
|
+
|
|
34
|
+
The API is file-based: send a prerecorded English audio file (WAV, FLAC, MP3,
|
|
35
|
+
M4A, OGG, or WebM; up to 30 MB / 60 minutes), get structured results back.
|
|
36
|
+
Usage is metered per second of audio.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install oruk
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
Create an account at [oruk.ai](https://oruk.ai/auth/signup) ($5 trial credit,
|
|
47
|
+
no card required) and create an API key in the developer portal.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import os
|
|
51
|
+
from oruk import Oruk
|
|
52
|
+
|
|
53
|
+
with Oruk(api_key=os.environ["ORUK_API_KEY"]) as client:
|
|
54
|
+
result = client.analyze("sample.wav", model="oruk-resonance")
|
|
55
|
+
|
|
56
|
+
print(result["text"]) # English transcript
|
|
57
|
+
print(result["emotions"]) # calibrated multilabel emotion scores
|
|
58
|
+
print(result["styles"]) # calibrated multilabel speaking-style scores
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Endpoints
|
|
62
|
+
|
|
63
|
+
| Method | API endpoint | Returns |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `client.transcribe(file)` | `POST /v1/audio/transcriptions` | English transcript |
|
|
66
|
+
| `client.emotions(file)` | `POST /v1/audio/emotions` | 15 calibrated emotion labels |
|
|
67
|
+
| `client.styles(file)` | `POST /v1/audio/styles` | 16 calibrated speaking-style labels |
|
|
68
|
+
| `client.affect(file)` | `POST /v1/audio/affect` | emotion + style, no transcript |
|
|
69
|
+
| `client.analyze(file)` | `POST /v1/audio/analysis` | transcript, labels, segments, tagged text |
|
|
70
|
+
|
|
71
|
+
Every method accepts a path, `Path`, or binary file object, plus optional
|
|
72
|
+
`model=` (`oruk-resonance`, `oruk-spectra-1`) and `request_id=` arguments.
|
|
73
|
+
|
|
74
|
+
The client sends a unique request ID per call and retries only 429 and
|
|
75
|
+
transient 5xx responses with jittered backoff. Errors raise `OrukAPIError`
|
|
76
|
+
with `status`, `code`, and `request_id` attributes.
|
|
77
|
+
|
|
78
|
+
## Links
|
|
79
|
+
|
|
80
|
+
- Documentation and API reference: <https://oruk.ai/docs>
|
|
81
|
+
- Capabilities and scope: <https://oruk.ai/capabilities>
|
|
82
|
+
- Pricing: <https://oruk.ai/pricing>
|
|
83
|
+
- Benchmarks: <https://oruk.ai/benchmarks/methodology>
|
|
84
|
+
- Service status: <https://oruk.ai/status>
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
oruk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# oruk — Python client for the oruk Speech API
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [oruk](https://oruk.ai), the speech lab building audio
|
|
4
|
+
models for English transcription, calibrated multilabel emotion detection,
|
|
5
|
+
speaking-style classification, and unified audio analysis.
|
|
6
|
+
|
|
7
|
+
The API is file-based: send a prerecorded English audio file (WAV, FLAC, MP3,
|
|
8
|
+
M4A, OGG, or WebM; up to 30 MB / 60 minutes), get structured results back.
|
|
9
|
+
Usage is metered per second of audio.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install oruk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
Create an account at [oruk.ai](https://oruk.ai/auth/signup) ($5 trial credit,
|
|
20
|
+
no card required) and create an API key in the developer portal.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import os
|
|
24
|
+
from oruk import Oruk
|
|
25
|
+
|
|
26
|
+
with Oruk(api_key=os.environ["ORUK_API_KEY"]) as client:
|
|
27
|
+
result = client.analyze("sample.wav", model="oruk-resonance")
|
|
28
|
+
|
|
29
|
+
print(result["text"]) # English transcript
|
|
30
|
+
print(result["emotions"]) # calibrated multilabel emotion scores
|
|
31
|
+
print(result["styles"]) # calibrated multilabel speaking-style scores
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Endpoints
|
|
35
|
+
|
|
36
|
+
| Method | API endpoint | Returns |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `client.transcribe(file)` | `POST /v1/audio/transcriptions` | English transcript |
|
|
39
|
+
| `client.emotions(file)` | `POST /v1/audio/emotions` | 15 calibrated emotion labels |
|
|
40
|
+
| `client.styles(file)` | `POST /v1/audio/styles` | 16 calibrated speaking-style labels |
|
|
41
|
+
| `client.affect(file)` | `POST /v1/audio/affect` | emotion + style, no transcript |
|
|
42
|
+
| `client.analyze(file)` | `POST /v1/audio/analysis` | transcript, labels, segments, tagged text |
|
|
43
|
+
|
|
44
|
+
Every method accepts a path, `Path`, or binary file object, plus optional
|
|
45
|
+
`model=` (`oruk-resonance`, `oruk-spectra-1`) and `request_id=` arguments.
|
|
46
|
+
|
|
47
|
+
The client sends a unique request ID per call and retries only 429 and
|
|
48
|
+
transient 5xx responses with jittered backoff. Errors raise `OrukAPIError`
|
|
49
|
+
with `status`, `code`, and `request_id` attributes.
|
|
50
|
+
|
|
51
|
+
## Links
|
|
52
|
+
|
|
53
|
+
- Documentation and API reference: <https://oruk.ai/docs>
|
|
54
|
+
- Capabilities and scope: <https://oruk.ai/capabilities>
|
|
55
|
+
- Pricing: <https://oruk.ai/pricing>
|
|
56
|
+
- Benchmarks: <https://oruk.ai/benchmarks/methodology>
|
|
57
|
+
- Service status: <https://oruk.ai/status>
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "oruk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python client for the oruk Speech API: English transcription, calibrated multilabel emotion and speaking-style labels, and unified audio analysis."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "oruk labs", email = "access@oruk.ai" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"speech",
|
|
15
|
+
"speech-to-text",
|
|
16
|
+
"transcription",
|
|
17
|
+
"speech emotion recognition",
|
|
18
|
+
"emotion detection",
|
|
19
|
+
"paralinguistics",
|
|
20
|
+
"speech understanding",
|
|
21
|
+
"audio analysis",
|
|
22
|
+
"oruk",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
34
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
35
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
36
|
+
]
|
|
37
|
+
dependencies = ["httpx>=0.27,<1"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://oruk.ai"
|
|
41
|
+
Documentation = "https://oruk.ai/docs"
|
|
42
|
+
Changelog = "https://oruk.ai/changelog"
|
|
43
|
+
Source = "https://github.com/Oruk-AI/oruk-speech-lab"
|
|
44
|
+
Pricing = "https://oruk.ai/pricing"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/oruk"]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import mimetypes
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, BinaryIO
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrukAPIError(RuntimeError):
|
|
14
|
+
def __init__(self, message: str, status: int, code: str | None = None, request_id: str | None = None):
|
|
15
|
+
super().__init__(message)
|
|
16
|
+
self.status = status
|
|
17
|
+
self.code = code
|
|
18
|
+
self.request_id = request_id
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Oruk:
|
|
22
|
+
_RETRYABLE = {429, 500, 502, 503, 504}
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
api_key: str,
|
|
27
|
+
*,
|
|
28
|
+
base_url: str = "https://speech-api.oruk.ai",
|
|
29
|
+
timeout: float = 120.0,
|
|
30
|
+
max_retries: int = 2,
|
|
31
|
+
) -> None:
|
|
32
|
+
if not api_key:
|
|
33
|
+
raise ValueError("api_key is required")
|
|
34
|
+
self.api_key = api_key
|
|
35
|
+
self.base_url = base_url.rstrip("/")
|
|
36
|
+
self.max_retries = max_retries
|
|
37
|
+
self._client = httpx.Client(timeout=timeout)
|
|
38
|
+
|
|
39
|
+
def close(self) -> None:
|
|
40
|
+
self._client.close()
|
|
41
|
+
|
|
42
|
+
def __enter__(self) -> "Oruk":
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def __exit__(self, *_: object) -> None:
|
|
46
|
+
self.close()
|
|
47
|
+
|
|
48
|
+
def transcribe(self, file: str | Path | BinaryIO, **kwargs: Any) -> dict[str, Any]:
|
|
49
|
+
return self.request("transcriptions", file, **kwargs)
|
|
50
|
+
|
|
51
|
+
def emotions(self, file: str | Path | BinaryIO, **kwargs: Any) -> dict[str, Any]:
|
|
52
|
+
return self.request("emotions", file, **kwargs)
|
|
53
|
+
|
|
54
|
+
def styles(self, file: str | Path | BinaryIO, **kwargs: Any) -> dict[str, Any]:
|
|
55
|
+
return self.request("styles", file, **kwargs)
|
|
56
|
+
|
|
57
|
+
def affect(self, file: str | Path | BinaryIO, **kwargs: Any) -> dict[str, Any]:
|
|
58
|
+
return self.request("affect", file, **kwargs)
|
|
59
|
+
|
|
60
|
+
def analyze(self, file: str | Path | BinaryIO, **kwargs: Any) -> dict[str, Any]:
|
|
61
|
+
return self.request("analysis", file, **kwargs)
|
|
62
|
+
|
|
63
|
+
def request(
|
|
64
|
+
self,
|
|
65
|
+
task: str,
|
|
66
|
+
file: str | Path | BinaryIO,
|
|
67
|
+
*,
|
|
68
|
+
model: str | None = None,
|
|
69
|
+
request_id: str | None = None,
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
request_id = request_id or str(uuid.uuid4())
|
|
72
|
+
owned_file: BinaryIO | None = None
|
|
73
|
+
if isinstance(file, (str, Path)):
|
|
74
|
+
path = Path(file)
|
|
75
|
+
owned_file = path.open("rb")
|
|
76
|
+
stream = owned_file
|
|
77
|
+
filename = path.name
|
|
78
|
+
else:
|
|
79
|
+
stream = file
|
|
80
|
+
filename = Path(getattr(file, "name", "audio.wav")).name
|
|
81
|
+
content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
|
82
|
+
try:
|
|
83
|
+
for attempt in range(self.max_retries + 1):
|
|
84
|
+
if hasattr(stream, "seek"):
|
|
85
|
+
stream.seek(0)
|
|
86
|
+
response = self._client.post(
|
|
87
|
+
f"{self.base_url}/v1/audio/{task}",
|
|
88
|
+
headers={
|
|
89
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
90
|
+
"X-Request-ID": request_id,
|
|
91
|
+
},
|
|
92
|
+
files={"file": (filename, stream, content_type)},
|
|
93
|
+
data={"model": model} if model else None,
|
|
94
|
+
)
|
|
95
|
+
if response.is_success:
|
|
96
|
+
return response.json()
|
|
97
|
+
try:
|
|
98
|
+
error = response.json().get("error", {})
|
|
99
|
+
except ValueError:
|
|
100
|
+
error = {}
|
|
101
|
+
failure = OrukAPIError(
|
|
102
|
+
error.get("message") or f"Oruk API returned HTTP {response.status_code}",
|
|
103
|
+
response.status_code,
|
|
104
|
+
error.get("code"),
|
|
105
|
+
error.get("request_id") or response.headers.get("x-request-id"),
|
|
106
|
+
)
|
|
107
|
+
if response.status_code not in self._RETRYABLE or attempt == self.max_retries:
|
|
108
|
+
raise failure
|
|
109
|
+
time.sleep(0.25 * (2**attempt) + random.random() * 0.1)
|
|
110
|
+
finally:
|
|
111
|
+
if owned_file:
|
|
112
|
+
owned_file.close()
|
|
113
|
+
raise RuntimeError("Oruk request failed")
|