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 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
+ )