orbitalsai 1.0.0__py3-none-any.whl
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.
- orbitalsai/__init__.py +65 -0
- orbitalsai/async_client.py +379 -0
- orbitalsai/client.py +368 -0
- orbitalsai/exceptions.py +58 -0
- orbitalsai/models.py +135 -0
- orbitalsai/utils.py +98 -0
- orbitalsai-1.0.0.dist-info/METADATA +439 -0
- orbitalsai-1.0.0.dist-info/RECORD +11 -0
- orbitalsai-1.0.0.dist-info/WHEEL +5 -0
- orbitalsai-1.0.0.dist-info/licenses/LICENSE +21 -0
- orbitalsai-1.0.0.dist-info/top_level.txt +1 -0
orbitalsai/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OrbitalsAI Python SDK
|
|
3
|
+
|
|
4
|
+
A simple and powerful Python SDK for the OrbitalsAI API.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
import orbitalsai
|
|
8
|
+
|
|
9
|
+
# Synchronous usage
|
|
10
|
+
client = orbitalsai.Client(api_key="your_api_key_here")
|
|
11
|
+
transcript = client.transcribe("audio.mp3")
|
|
12
|
+
print(transcript.text)
|
|
13
|
+
|
|
14
|
+
# Asynchronous usage
|
|
15
|
+
async with orbitalsai.AsyncClient(api_key="your_api_key_here") as client:
|
|
16
|
+
transcript = await client.transcribe("audio.mp3")
|
|
17
|
+
print(transcript.text)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .client import Client
|
|
21
|
+
from .async_client import AsyncClient
|
|
22
|
+
from .models import (
|
|
23
|
+
TranscriptTask, Transcript, Balance, UsageHistory, DailyUsage, User,
|
|
24
|
+
SUPPORTED_LANGUAGES, SUPPORTED_AUDIO_FORMATS, SUPPORTED_AUDIO_MIMETYPES
|
|
25
|
+
)
|
|
26
|
+
from .exceptions import (
|
|
27
|
+
OrbitalsAIError, AuthenticationError, InsufficientBalanceError,
|
|
28
|
+
FileNotFoundError, UnsupportedFileError, UnsupportedLanguageError,
|
|
29
|
+
TaskNotFoundError, TranscriptionError, TimeoutError, APIError
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__version__ = "1.0.0"
|
|
33
|
+
__author__ = "OrbitalsAI"
|
|
34
|
+
__email__ = "support@orbitalsai.com"
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Clients
|
|
38
|
+
"Client",
|
|
39
|
+
"AsyncClient",
|
|
40
|
+
|
|
41
|
+
# Models
|
|
42
|
+
"TranscriptTask",
|
|
43
|
+
"Transcript",
|
|
44
|
+
"Balance",
|
|
45
|
+
"UsageHistory",
|
|
46
|
+
"DailyUsage",
|
|
47
|
+
"User",
|
|
48
|
+
|
|
49
|
+
# Constants
|
|
50
|
+
"SUPPORTED_LANGUAGES",
|
|
51
|
+
"SUPPORTED_AUDIO_FORMATS",
|
|
52
|
+
"SUPPORTED_AUDIO_MIMETYPES",
|
|
53
|
+
|
|
54
|
+
# Exceptions
|
|
55
|
+
"OrbitalsAIError",
|
|
56
|
+
"AuthenticationError",
|
|
57
|
+
"InsufficientBalanceError",
|
|
58
|
+
"FileNotFoundError",
|
|
59
|
+
"UnsupportedFileError",
|
|
60
|
+
"UnsupportedLanguageError",
|
|
61
|
+
"TaskNotFoundError",
|
|
62
|
+
"TranscriptionError",
|
|
63
|
+
"TimeoutError",
|
|
64
|
+
"APIError",
|
|
65
|
+
]
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OrbitalsAI Asynchronous Client
|
|
3
|
+
|
|
4
|
+
Asynchronous client for the OrbitalsAI API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import aiohttp
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
from datetime import datetime, date
|
|
11
|
+
|
|
12
|
+
from .models import (
|
|
13
|
+
TranscriptTask, Transcript, Balance, UsageHistory, DailyUsage,
|
|
14
|
+
User, APIKey, UsageRecord, DailyUsageRecord
|
|
15
|
+
)
|
|
16
|
+
from .exceptions import (
|
|
17
|
+
OrbitalsAIError, AuthenticationError, InsufficientBalanceError,
|
|
18
|
+
FileNotFoundError, UnsupportedFileError, UnsupportedLanguageError,
|
|
19
|
+
TaskNotFoundError, TranscriptionError, TimeoutError, APIError
|
|
20
|
+
)
|
|
21
|
+
from .utils import validate_audio_file, validate_language, get_file_size
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncClient:
|
|
25
|
+
"""
|
|
26
|
+
Asynchronous client for the OrbitalsAI API.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
async with orbitalsai.AsyncClient(api_key="your_api_key_here") as client:
|
|
30
|
+
transcript = await client.transcribe("audio.mp3")
|
|
31
|
+
print(transcript.text)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, api_key: str, base_url: str = "https://api.orbitalsai.com/api/v1"):
|
|
35
|
+
"""
|
|
36
|
+
Initialize the OrbitalsAI async client.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
api_key: Your OrbitalsAI API key
|
|
40
|
+
base_url: Base URL for the API (default: localhost for development)
|
|
41
|
+
"""
|
|
42
|
+
self.api_key = api_key
|
|
43
|
+
self.base_url = base_url.rstrip('/')
|
|
44
|
+
self.session = None
|
|
45
|
+
|
|
46
|
+
async def __aenter__(self):
|
|
47
|
+
"""Async context manager entry."""
|
|
48
|
+
self.session = aiohttp.ClientSession(
|
|
49
|
+
headers={
|
|
50
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
51
|
+
"User-Agent": "orbitalsai-python-sdk/1.0.0"
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
57
|
+
"""Async context manager exit."""
|
|
58
|
+
if self.session:
|
|
59
|
+
await self.session.close()
|
|
60
|
+
|
|
61
|
+
async def _make_request(self, method: str, endpoint: str, **kwargs) -> dict:
|
|
62
|
+
"""
|
|
63
|
+
Make an HTTP request to the API.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
method: HTTP method (GET, POST, etc.)
|
|
67
|
+
endpoint: API endpoint (without base URL)
|
|
68
|
+
**kwargs: Additional arguments for aiohttp
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
JSON response data
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
APIError: If the API returns an error
|
|
75
|
+
"""
|
|
76
|
+
if not self.session:
|
|
77
|
+
raise OrbitalsAIError("Client not initialized. Use 'async with' context manager.")
|
|
78
|
+
|
|
79
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
async with self.session.request(method, url, **kwargs) as response:
|
|
83
|
+
if response.status == 401:
|
|
84
|
+
raise AuthenticationError("Invalid API key")
|
|
85
|
+
elif response.status == 402:
|
|
86
|
+
raise InsufficientBalanceError("Insufficient balance")
|
|
87
|
+
elif response.status == 404:
|
|
88
|
+
raise TaskNotFoundError("Task not found")
|
|
89
|
+
elif response.status >= 400:
|
|
90
|
+
try:
|
|
91
|
+
error_data = await response.json()
|
|
92
|
+
raise APIError(
|
|
93
|
+
error_data.get("detail", f"HTTP {response.status}"),
|
|
94
|
+
status_code=response.status,
|
|
95
|
+
response_data=error_data
|
|
96
|
+
)
|
|
97
|
+
except ValueError:
|
|
98
|
+
raise APIError(f"HTTP {response.status}", status_code=response.status)
|
|
99
|
+
|
|
100
|
+
return await response.json()
|
|
101
|
+
except aiohttp.ClientError as e:
|
|
102
|
+
raise OrbitalsAIError(f"Request failed: {str(e)}")
|
|
103
|
+
|
|
104
|
+
async def transcribe(
|
|
105
|
+
self,
|
|
106
|
+
file_path: str,
|
|
107
|
+
language: str = "english",
|
|
108
|
+
generate_srt: bool = False,
|
|
109
|
+
wait: bool = True,
|
|
110
|
+
timeout: int = 300,
|
|
111
|
+
poll_interval: int = 5
|
|
112
|
+
) -> Transcript:
|
|
113
|
+
"""
|
|
114
|
+
Transcribe an audio file.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
file_path: Path to the audio file
|
|
118
|
+
language: Language of the audio (default: "english")
|
|
119
|
+
generate_srt: Whether to generate SRT subtitles (default: False)
|
|
120
|
+
wait: Whether to wait for completion (default: True)
|
|
121
|
+
timeout: Maximum time to wait in seconds (default: 300)
|
|
122
|
+
poll_interval: Seconds to wait between status checks (default: 5)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Transcript object with the result
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
FileNotFoundError: If the audio file is not found
|
|
129
|
+
UnsupportedFileError: If the file format is not supported
|
|
130
|
+
UnsupportedLanguageError: If the language is not supported
|
|
131
|
+
TimeoutError: If the operation times out
|
|
132
|
+
TranscriptionError: If transcription fails
|
|
133
|
+
"""
|
|
134
|
+
# Validate inputs
|
|
135
|
+
validate_audio_file(file_path)
|
|
136
|
+
validate_language(language)
|
|
137
|
+
|
|
138
|
+
# Prepare file upload
|
|
139
|
+
with open(file_path, 'rb') as f:
|
|
140
|
+
data = aiohttp.FormData()
|
|
141
|
+
data.add_field('file', f, filename=file_path.split('/')[-1], content_type='audio/mpeg')
|
|
142
|
+
data.add_field('language', language)
|
|
143
|
+
data.add_field('generate_srt', str(generate_srt).lower())
|
|
144
|
+
|
|
145
|
+
# Upload file
|
|
146
|
+
response = await self._make_request("POST", "/audio/upload", data=data)
|
|
147
|
+
task_id = response["task_id"]
|
|
148
|
+
|
|
149
|
+
if not wait:
|
|
150
|
+
# Return task immediately
|
|
151
|
+
task = await self.get_task(task_id)
|
|
152
|
+
return Transcript(
|
|
153
|
+
text=task.result_text or "",
|
|
154
|
+
srt_content=task.srt_content,
|
|
155
|
+
task_id=task_id,
|
|
156
|
+
original_filename=task.original_filename,
|
|
157
|
+
audio_url=task.audio_url
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Wait for completion
|
|
161
|
+
return await self.wait_for_task(task_id, timeout, poll_interval)
|
|
162
|
+
|
|
163
|
+
async def get_task(self, task_id: int) -> TranscriptTask:
|
|
164
|
+
"""
|
|
165
|
+
Get the status of a transcription task.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
task_id: ID of the task
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
TranscriptTask object with current status
|
|
172
|
+
"""
|
|
173
|
+
response = await self._make_request("GET", f"/audio/status/{task_id}")
|
|
174
|
+
|
|
175
|
+
return TranscriptTask(
|
|
176
|
+
task_id=task_id,
|
|
177
|
+
status=response["status"],
|
|
178
|
+
original_filename=response["original_filename"],
|
|
179
|
+
audio_url=response.get("audio_url"),
|
|
180
|
+
srt_requested=response["srt_requested"],
|
|
181
|
+
result_text=response.get("result_text"),
|
|
182
|
+
srt_content=response.get("srt_content"),
|
|
183
|
+
error=response.get("error")
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
async def wait_for_task(
|
|
187
|
+
self,
|
|
188
|
+
task_id: int,
|
|
189
|
+
timeout: int = 300,
|
|
190
|
+
poll_interval: int = 5
|
|
191
|
+
) -> Transcript:
|
|
192
|
+
"""
|
|
193
|
+
Wait for a transcription task to complete.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
task_id: ID of the task
|
|
197
|
+
timeout: Maximum time to wait in seconds
|
|
198
|
+
poll_interval: Seconds to wait between status checks
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Transcript object with the result
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
TimeoutError: If the operation times out
|
|
205
|
+
TranscriptionError: If transcription fails
|
|
206
|
+
"""
|
|
207
|
+
start_time = asyncio.get_event_loop().time()
|
|
208
|
+
|
|
209
|
+
while asyncio.get_event_loop().time() - start_time < timeout:
|
|
210
|
+
task = await self.get_task(task_id)
|
|
211
|
+
|
|
212
|
+
if task.status == "completed":
|
|
213
|
+
return Transcript(
|
|
214
|
+
text=task.result_text or "",
|
|
215
|
+
srt_content=task.srt_content,
|
|
216
|
+
task_id=task_id,
|
|
217
|
+
original_filename=task.original_filename,
|
|
218
|
+
audio_url=task.audio_url
|
|
219
|
+
)
|
|
220
|
+
elif task.status == "failed":
|
|
221
|
+
raise TranscriptionError(f"Transcription failed: {task.error}")
|
|
222
|
+
|
|
223
|
+
await asyncio.sleep(poll_interval)
|
|
224
|
+
|
|
225
|
+
raise TimeoutError(f"Task {task_id} did not complete within {timeout} seconds")
|
|
226
|
+
|
|
227
|
+
async def list_tasks(self) -> List[TranscriptTask]:
|
|
228
|
+
"""
|
|
229
|
+
Get all transcription tasks for the current user.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
List of TranscriptTask objects
|
|
233
|
+
"""
|
|
234
|
+
response = await self._make_request("GET", "/audio/tasks")
|
|
235
|
+
|
|
236
|
+
tasks = []
|
|
237
|
+
for task_data in response:
|
|
238
|
+
tasks.append(TranscriptTask(
|
|
239
|
+
task_id=task_data["task_id"],
|
|
240
|
+
status=task_data["status"],
|
|
241
|
+
original_filename=task_data["original_filename"],
|
|
242
|
+
audio_url=task_data.get("task_blob_directory"),
|
|
243
|
+
srt_requested=task_data["srt_requested"],
|
|
244
|
+
created_at=datetime.fromisoformat(task_data["created_at"].replace('Z', '+00:00'))
|
|
245
|
+
))
|
|
246
|
+
|
|
247
|
+
return tasks
|
|
248
|
+
|
|
249
|
+
async def get_balance(self) -> Balance:
|
|
250
|
+
"""
|
|
251
|
+
Get the current user's balance.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Balance object with current balance information
|
|
255
|
+
"""
|
|
256
|
+
response = await self._make_request("GET", "/billing/balance")
|
|
257
|
+
|
|
258
|
+
return Balance(
|
|
259
|
+
balance=response["balance"],
|
|
260
|
+
last_updated=datetime.fromisoformat(response["last_updated"].replace('Z', '+00:00'))
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
async def get_usage_history(
|
|
264
|
+
self,
|
|
265
|
+
start_date: Optional[datetime] = None,
|
|
266
|
+
end_date: Optional[datetime] = None,
|
|
267
|
+
page: int = 1,
|
|
268
|
+
page_size: int = 50
|
|
269
|
+
) -> UsageHistory:
|
|
270
|
+
"""
|
|
271
|
+
Get usage history for the current user.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
start_date: Start date for the history (default: 30 days ago)
|
|
275
|
+
end_date: End date for the history (default: now)
|
|
276
|
+
page: Page number (default: 1)
|
|
277
|
+
page_size: Number of records per page (default: 50)
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
UsageHistory object with usage records
|
|
281
|
+
"""
|
|
282
|
+
params = {"page": page, "page_size": page_size}
|
|
283
|
+
if start_date:
|
|
284
|
+
params["start_date"] = start_date.isoformat()
|
|
285
|
+
if end_date:
|
|
286
|
+
params["end_date"] = end_date.isoformat()
|
|
287
|
+
|
|
288
|
+
response = await self._make_request("GET", "/billing/usage-history", params=params)
|
|
289
|
+
|
|
290
|
+
records = []
|
|
291
|
+
for record_data in response["records"]:
|
|
292
|
+
records.append(UsageRecord(
|
|
293
|
+
id=record_data["id"],
|
|
294
|
+
service_type=record_data["service_type"],
|
|
295
|
+
usage_amount=record_data["total_audio_usage"],
|
|
296
|
+
cost=record_data["cost"],
|
|
297
|
+
timestamp=datetime.fromisoformat(record_data["timestamp"].replace('Z', '+00:00')),
|
|
298
|
+
api_key_id=record_data.get("api_key_id")
|
|
299
|
+
))
|
|
300
|
+
|
|
301
|
+
return UsageHistory(
|
|
302
|
+
records=records,
|
|
303
|
+
total_records=response["total_records"],
|
|
304
|
+
total_pages=response["total_pages"],
|
|
305
|
+
current_page=response["current_page"],
|
|
306
|
+
start_date=datetime.fromisoformat(response["start_date"].replace('Z', '+00:00')),
|
|
307
|
+
end_date=datetime.fromisoformat(response["end_date"].replace('Z', '+00:00')),
|
|
308
|
+
period_summary=response["period_summary"]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async def get_daily_usage(
|
|
312
|
+
self,
|
|
313
|
+
start_date: Optional[date] = None,
|
|
314
|
+
end_date: Optional[date] = None,
|
|
315
|
+
page: int = 1,
|
|
316
|
+
page_size: int = 30
|
|
317
|
+
) -> DailyUsage:
|
|
318
|
+
"""
|
|
319
|
+
Get daily usage history for the current user.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
start_date: Start date for the history (default: 30 days ago)
|
|
323
|
+
end_date: End date for the history (default: today)
|
|
324
|
+
page: Page number (default: 1)
|
|
325
|
+
page_size: Number of records per page (default: 30)
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
DailyUsage object with daily usage records
|
|
329
|
+
"""
|
|
330
|
+
params = {"page": page, "page_size": page_size}
|
|
331
|
+
if start_date:
|
|
332
|
+
params["start_date"] = start_date.isoformat()
|
|
333
|
+
if end_date:
|
|
334
|
+
params["end_date"] = end_date.isoformat()
|
|
335
|
+
|
|
336
|
+
response = await self._make_request("GET", "/billing/daily-usage", params=params)
|
|
337
|
+
|
|
338
|
+
daily_records = []
|
|
339
|
+
for record_data in response["records"]:
|
|
340
|
+
daily_records.append(DailyUsageRecord(
|
|
341
|
+
date=date.fromisoformat(record_data["date"]),
|
|
342
|
+
total_cost=record_data["total_cost"],
|
|
343
|
+
total_audio_usage=record_data["transcription_usage"],
|
|
344
|
+
record_count=1, # Each record represents one day
|
|
345
|
+
transcription_usage=record_data.get("transcription_usage", 0.0),
|
|
346
|
+
transcription_cost=record_data.get("transcription_cost", 0.0),
|
|
347
|
+
translation_usage=record_data.get("translation_usage", 0.0),
|
|
348
|
+
translation_cost=record_data.get("translation_cost", 0.0),
|
|
349
|
+
summarization_usage=record_data.get("summarization_usage", 0.0),
|
|
350
|
+
summarization_cost=record_data.get("summarization_cost", 0.0)
|
|
351
|
+
))
|
|
352
|
+
|
|
353
|
+
return DailyUsage(
|
|
354
|
+
daily_records=daily_records,
|
|
355
|
+
total_records=response["total_records"],
|
|
356
|
+
total_pages=response["total_pages"],
|
|
357
|
+
current_page=response["current_page"],
|
|
358
|
+
start_date=date.fromisoformat(response["start_date"]),
|
|
359
|
+
end_date=date.fromisoformat(response["end_date"]),
|
|
360
|
+
total_cost=response["period_summary"]["total_cost"],
|
|
361
|
+
total_audio_seconds=response["period_summary"]["total_transcription_usage"]
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
async def get_user(self) -> User:
|
|
365
|
+
"""
|
|
366
|
+
Get current user details.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
User object with user information
|
|
370
|
+
"""
|
|
371
|
+
response = await self._make_request("GET", "/user/")
|
|
372
|
+
|
|
373
|
+
return User(
|
|
374
|
+
id=response["id"],
|
|
375
|
+
email=response["email"],
|
|
376
|
+
first_name=response["first_name"],
|
|
377
|
+
last_name=response["last_name"],
|
|
378
|
+
is_verified=response["is_verified"]
|
|
379
|
+
)
|