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 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,3 @@
1
+ from .client import Oruk, OrukAPIError
2
+
3
+ __all__ = ["Oruk", "OrukAPIError"]
@@ -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")