speechcortex-sdk 0.1.0__tar.gz → 0.1.dev2__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.
- {speechcortex_sdk-0.1.0/speechcortex_sdk.egg-info → speechcortex_sdk-0.1.dev2}/PKG-INFO +8 -8
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/README.md +2 -2
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/pyproject.toml +6 -6
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/requirements.txt +2 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/setup.py +5 -5
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/__init__.py +25 -1
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/client.py +21 -2
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/__init__.py +25 -0
- speechcortex_sdk-0.1.dev2/speechcortex/clients/transcribe/batch/__init__.py +32 -0
- speechcortex_sdk-0.1.dev2/speechcortex/clients/transcribe/batch/client.py +564 -0
- speechcortex_sdk-0.1.dev2/speechcortex/clients/transcribe/batch/exceptions.py +59 -0
- speechcortex_sdk-0.1.dev2/speechcortex/clients/transcribe/batch/options.py +88 -0
- speechcortex_sdk-0.1.dev2/speechcortex/clients/transcribe/batch/response.py +149 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/options.py +3 -1
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2/speechcortex_sdk.egg-info}/PKG-INFO +8 -8
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex_sdk.egg-info/SOURCES.txt +5 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/LICENSE +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/MANIFEST.in +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/requirements-dev.txt +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/setup.cfg +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/__init__.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/__init__.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/constants.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/errors.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/microphone.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/__init__.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/enums.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/realtime/__init__.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/realtime/client.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/realtime/options.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/realtime/response.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/errors.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/utils/__init__.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/utils/verboselogs/__init__.py +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex_sdk.egg-info/dependency_links.txt +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex_sdk.egg-info/requires.txt +0 -0
- {speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex_sdk.egg-info/top_level.txt +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: speechcortex-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev2
|
|
4
4
|
Summary: The official Python SDK for SpeechCortex ASR platform.
|
|
5
|
-
Home-page: https://github.com/
|
|
5
|
+
Home-page: https://github.com/speechcortex/speechcortex-sdk
|
|
6
6
|
Author: SpeechCortex Team
|
|
7
7
|
Author-email: SpeechCortex Team <team@speechcortex.com>
|
|
8
8
|
License: MIT
|
|
9
|
-
Project-URL: Homepage, https://github.com/
|
|
10
|
-
Project-URL: Bug Tracker, https://github.com/
|
|
11
|
-
Project-URL: Source Code, https://github.com/
|
|
12
|
-
Project-URL: Documentation, https://github.com/
|
|
9
|
+
Project-URL: Homepage, https://github.com/speechcortex/speechcortex-sdk
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/speechcortex/speechcortex-sdk/issues
|
|
11
|
+
Project-URL: Source Code, https://github.com/speechcortex/speechcortex-sdk
|
|
12
|
+
Project-URL: Documentation, https://github.com/speechcortex/speechcortex-sdk#readme
|
|
13
13
|
Keywords: speechcortex,asr,speech-to-text,speech recognition
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -35,7 +35,7 @@ Dynamic: requires-python
|
|
|
35
35
|
|
|
36
36
|
# SpeechCortex Python SDK
|
|
37
37
|
|
|
38
|
-
[](https://github.com/speechcortex/speechcortex-sdk/actions/workflows/ci.yml)
|
|
39
39
|
[](https://badge.fury.io/py/speechcortex-sdk)
|
|
40
40
|
[](https://pypi.org/project/speechcortex-sdk/)
|
|
41
41
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -57,7 +57,7 @@ Official Python SDK for SpeechCortex ASR (Automatic Speech Recognition) platform
|
|
|
57
57
|
## Installation
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
pip install "git+https://github.com/
|
|
60
|
+
pip install "git+https://github.com/speechcortex/speechcortex-sdk.git@package_init"
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SpeechCortex Python SDK
|
|
2
2
|
|
|
3
|
-
[](https://github.com/speechcortex/speechcortex-sdk/actions/workflows/ci.yml)
|
|
4
4
|
[](https://badge.fury.io/py/speechcortex-sdk)
|
|
5
5
|
[](https://pypi.org/project/speechcortex-sdk/)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -22,7 +22,7 @@ Official Python SDK for SpeechCortex ASR (Automatic Speech Recognition) platform
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
pip install "git+https://github.com/
|
|
25
|
+
pip install "git+https://github.com/speechcortex/speechcortex-sdk.git@package_init"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
```bash
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "speechcortex-sdk"
|
|
10
|
-
version = "0.1.
|
|
10
|
+
version = "0.1.dev2"
|
|
11
11
|
description = "The official Python SDK for SpeechCortex ASR platform."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.10"
|
|
@@ -36,17 +36,17 @@ dependencies = [
|
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
[project.urls]
|
|
39
|
-
"Homepage" = "https://github.com/
|
|
40
|
-
"Bug Tracker" = "https://github.com/
|
|
41
|
-
"Source Code" = "https://github.com/
|
|
42
|
-
"Documentation" = "https://github.com/
|
|
39
|
+
"Homepage" = "https://github.com/speechcortex/speechcortex-sdk"
|
|
40
|
+
"Bug Tracker" = "https://github.com/speechcortex/speechcortex-sdk/issues"
|
|
41
|
+
"Source Code" = "https://github.com/speechcortex/speechcortex-sdk"
|
|
42
|
+
"Documentation" = "https://github.com/speechcortex/speechcortex-sdk#readme"
|
|
43
43
|
|
|
44
44
|
######
|
|
45
45
|
# poetry configuration (optional)
|
|
46
46
|
######
|
|
47
47
|
[tool.poetry]
|
|
48
48
|
name = "speechcortex-sdk"
|
|
49
|
-
version = "0.1.
|
|
49
|
+
version = "0.1.dev2"
|
|
50
50
|
description = "The official Python SDK for SpeechCortex ASR platform."
|
|
51
51
|
authors = ["SpeechCortex Team <team@speechcortex.com>"]
|
|
52
52
|
license = "MIT"
|
|
@@ -15,14 +15,14 @@ DESCRIPTION = "The official Python SDK for SpeechCortex ASR platform."
|
|
|
15
15
|
|
|
16
16
|
setup(
|
|
17
17
|
name="speechcortex-sdk",
|
|
18
|
-
version="0.1.
|
|
18
|
+
version="0.1.dev2",
|
|
19
19
|
author="SpeechCortex Team",
|
|
20
20
|
author_email="team@speechcortex.com",
|
|
21
|
-
url="https://github.com/
|
|
21
|
+
url="https://github.com/speechcortex/speechcortex-sdk",
|
|
22
22
|
project_urls={
|
|
23
|
-
"Bug Tracker": "https://github.com/
|
|
24
|
-
"Source Code": "https://github.com/
|
|
25
|
-
"Documentation": "https://github.com/
|
|
23
|
+
"Bug Tracker": "https://github.com/speechcortex/speechcortex-sdk/issues",
|
|
24
|
+
"Source Code": "https://github.com/speechcortex/speechcortex-sdk",
|
|
25
|
+
"Documentation": "https://github.com/speechcortex/speechcortex-sdk#readme",
|
|
26
26
|
},
|
|
27
27
|
description=DESCRIPTION,
|
|
28
28
|
long_description=LONG_DESCRIPTION,
|
|
@@ -8,7 +8,7 @@ SpeechCortex Python SDK
|
|
|
8
8
|
Official Python SDK for SpeechCortex ASR (Automatic Speech Recognition) platform.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
__version__ = "0.1.
|
|
11
|
+
__version__ = "0.1.dev2"
|
|
12
12
|
|
|
13
13
|
# Core client
|
|
14
14
|
from .client import SpeechCortexClient, SpeechCortex, TranscribeRouter
|
|
@@ -46,6 +46,18 @@ from .clients.transcribe import (
|
|
|
46
46
|
Alternative,
|
|
47
47
|
Channel,
|
|
48
48
|
Word,
|
|
49
|
+
# Batch transcription
|
|
50
|
+
BatchClient,
|
|
51
|
+
BatchOptions,
|
|
52
|
+
TranscriptionConfig,
|
|
53
|
+
JobDetails,
|
|
54
|
+
TranscriptionResult,
|
|
55
|
+
TranscriptionStatus,
|
|
56
|
+
BatchError,
|
|
57
|
+
JobNotFoundError,
|
|
58
|
+
JobFailedError,
|
|
59
|
+
TranscriptionNotReadyError,
|
|
60
|
+
BatchTimeoutError,
|
|
49
61
|
)
|
|
50
62
|
|
|
51
63
|
# Audio utilities
|
|
@@ -105,6 +117,18 @@ __all__ = [
|
|
|
105
117
|
"Alternative",
|
|
106
118
|
"Channel",
|
|
107
119
|
"Word",
|
|
120
|
+
# Batch transcription
|
|
121
|
+
"BatchClient",
|
|
122
|
+
"BatchOptions",
|
|
123
|
+
"TranscriptionConfig",
|
|
124
|
+
"JobDetails",
|
|
125
|
+
"TranscriptionResult",
|
|
126
|
+
"TranscriptionStatus",
|
|
127
|
+
"BatchError",
|
|
128
|
+
"JobNotFoundError",
|
|
129
|
+
"JobFailedError",
|
|
130
|
+
"TranscriptionNotReadyError",
|
|
131
|
+
"BatchTimeoutError",
|
|
108
132
|
# Audio
|
|
109
133
|
"Microphone",
|
|
110
134
|
"SpeechCortexMicrophoneError",
|
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|
|
6
6
|
|
|
7
7
|
from .options import SpeechCortexClientOptions
|
|
8
8
|
from .errors import SpeechCortexError, SpeechCortexApiKeyError
|
|
9
|
-
from .clients.transcribe import RealtimeClient
|
|
9
|
+
from .clients.transcribe import RealtimeClient, BatchClient
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class TranscribeRouter:
|
|
@@ -15,6 +15,7 @@ class TranscribeRouter:
|
|
|
15
15
|
def __init__(self, config: SpeechCortexClientOptions):
|
|
16
16
|
self._config = config
|
|
17
17
|
self._realtime_client: Optional[RealtimeClient] = None
|
|
18
|
+
self._batch_client: Optional[BatchClient] = None
|
|
18
19
|
|
|
19
20
|
def realtime(self) -> RealtimeClient:
|
|
20
21
|
"""
|
|
@@ -26,6 +27,17 @@ class TranscribeRouter:
|
|
|
26
27
|
if self._realtime_client is None:
|
|
27
28
|
self._realtime_client = RealtimeClient(self._config)
|
|
28
29
|
return self._realtime_client
|
|
30
|
+
|
|
31
|
+
def batch(self) -> BatchClient:
|
|
32
|
+
"""
|
|
33
|
+
Get a batch/post-call transcription client.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
BatchClient instance for batch transcription
|
|
37
|
+
"""
|
|
38
|
+
if self._batch_client is None:
|
|
39
|
+
self._batch_client = BatchClient(self._config)
|
|
40
|
+
return self._batch_client
|
|
29
41
|
|
|
30
42
|
|
|
31
43
|
class _LegacyWebsocket:
|
|
@@ -95,7 +107,14 @@ class SpeechCortexClient:
|
|
|
95
107
|
Access transcription services.
|
|
96
108
|
|
|
97
109
|
Returns:
|
|
98
|
-
TranscribeRouter for accessing real-time and batch transcription
|
|
110
|
+
TranscribeRouter for accessing real-time and batch transcription.
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> # Real-time transcription
|
|
114
|
+
>>> client.transcribe.realtime()
|
|
115
|
+
>>>
|
|
116
|
+
>>> # Batch/post-call transcription
|
|
117
|
+
>>> client.transcribe.batch()
|
|
99
118
|
"""
|
|
100
119
|
if self._transcribe is None:
|
|
101
120
|
self._transcribe = TranscribeRouter(self._config)
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/__init__.py
RENAMED
|
@@ -19,6 +19,19 @@ from .realtime import (
|
|
|
19
19
|
Channel,
|
|
20
20
|
Word,
|
|
21
21
|
)
|
|
22
|
+
from .batch import (
|
|
23
|
+
BatchClient,
|
|
24
|
+
BatchOptions,
|
|
25
|
+
TranscriptionConfig,
|
|
26
|
+
JobDetails,
|
|
27
|
+
TranscriptionResult,
|
|
28
|
+
TranscriptionStatus,
|
|
29
|
+
BatchError,
|
|
30
|
+
JobNotFoundError,
|
|
31
|
+
JobFailedError,
|
|
32
|
+
TranscriptionNotReadyError,
|
|
33
|
+
BatchTimeoutError,
|
|
34
|
+
)
|
|
22
35
|
|
|
23
36
|
__all__ = [
|
|
24
37
|
"LiveTranscriptionEvents",
|
|
@@ -36,4 +49,16 @@ __all__ = [
|
|
|
36
49
|
"Alternative",
|
|
37
50
|
"Channel",
|
|
38
51
|
"Word",
|
|
52
|
+
# Batch transcription
|
|
53
|
+
"BatchClient",
|
|
54
|
+
"BatchOptions",
|
|
55
|
+
"TranscriptionConfig",
|
|
56
|
+
"JobDetails",
|
|
57
|
+
"TranscriptionResult",
|
|
58
|
+
"TranscriptionStatus",
|
|
59
|
+
"BatchError",
|
|
60
|
+
"JobNotFoundError",
|
|
61
|
+
"JobFailedError",
|
|
62
|
+
"TranscriptionNotReadyError",
|
|
63
|
+
"BatchTimeoutError",
|
|
39
64
|
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright 2024 SpeechCortex SDK contributors. All Rights Reserved.
|
|
2
|
+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
from .client import BatchClient
|
|
6
|
+
from .options import BatchOptions, TranscriptionConfig
|
|
7
|
+
from .response import (
|
|
8
|
+
JobDetails,
|
|
9
|
+
TranscriptionResult,
|
|
10
|
+
TranscriptionStatus,
|
|
11
|
+
)
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
BatchError,
|
|
14
|
+
JobNotFoundError,
|
|
15
|
+
JobFailedError,
|
|
16
|
+
TranscriptionNotReadyError,
|
|
17
|
+
BatchTimeoutError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"BatchClient",
|
|
22
|
+
"BatchOptions",
|
|
23
|
+
"TranscriptionConfig",
|
|
24
|
+
"JobDetails",
|
|
25
|
+
"TranscriptionResult",
|
|
26
|
+
"TranscriptionStatus",
|
|
27
|
+
"BatchError",
|
|
28
|
+
"JobNotFoundError",
|
|
29
|
+
"JobFailedError",
|
|
30
|
+
"TranscriptionNotReadyError",
|
|
31
|
+
"BatchTimeoutError",
|
|
32
|
+
]
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# Copyright 2024 SpeechCortex SDK contributors. All Rights Reserved.
|
|
2
|
+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Async batch transcription client for SpeechCortex SDK.
|
|
7
|
+
|
|
8
|
+
This client provides a pure async interface for post-call/batch transcription,
|
|
9
|
+
allowing users to submit audio files or presigned URLs, poll for status, and
|
|
10
|
+
retrieve transcription results using aiohttp.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, BinaryIO, Dict, Optional, Union
|
|
21
|
+
from uuid import UUID
|
|
22
|
+
|
|
23
|
+
import aiohttp
|
|
24
|
+
import aiofiles
|
|
25
|
+
|
|
26
|
+
from ....options import SpeechCortexClientOptions
|
|
27
|
+
from ....errors import SpeechCortexError, SpeechCortexApiKeyError
|
|
28
|
+
from .options import BatchOptions, TranscriptionConfig
|
|
29
|
+
from .response import JobDetails, TranscriptionResult, TranscriptionStatus
|
|
30
|
+
from .exceptions import (
|
|
31
|
+
BatchError,
|
|
32
|
+
JobNotFoundError,
|
|
33
|
+
JobFailedError,
|
|
34
|
+
TranscriptionNotReadyError,
|
|
35
|
+
BatchTimeoutError,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
logger.addHandler(logging.NullHandler())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BatchClient:
|
|
44
|
+
"""
|
|
45
|
+
Async client for SpeechCortex batch/post-call transcription.
|
|
46
|
+
|
|
47
|
+
This client provides a pure async interface for submitting transcription jobs,
|
|
48
|
+
polling for status, and retrieving results. It uses aiohttp for non-blocking
|
|
49
|
+
HTTP communication.
|
|
50
|
+
|
|
51
|
+
Supports async context manager for automatic resource cleanup.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> from speechcortex import SpeechCortexClient
|
|
55
|
+
>>>
|
|
56
|
+
>>> client = SpeechCortexClient(api_key="your_key")
|
|
57
|
+
>>> batch = client.transcribe.batch()
|
|
58
|
+
>>>
|
|
59
|
+
>>> async with batch:
|
|
60
|
+
... job = await batch.submit_job(
|
|
61
|
+
... presigned_url="https://example.com/audio.mp3",
|
|
62
|
+
... language="en-US"
|
|
63
|
+
... )
|
|
64
|
+
... result = await batch.wait_for_completion(job.job_id)
|
|
65
|
+
... print(result.transcription)
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, config: SpeechCortexClientOptions) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Initialize the batch transcription client.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
config: SpeechCortexClientOptions containing API key, URL, etc.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
SpeechCortexError: If config is None.
|
|
77
|
+
SpeechCortexApiKeyError: If API key is missing.
|
|
78
|
+
"""
|
|
79
|
+
if config is None:
|
|
80
|
+
raise SpeechCortexError("Config is required")
|
|
81
|
+
|
|
82
|
+
if not config.api_key:
|
|
83
|
+
raise SpeechCortexApiKeyError("API key is required for batch transcription")
|
|
84
|
+
|
|
85
|
+
self._config = config
|
|
86
|
+
self._api_key = config.api_key
|
|
87
|
+
|
|
88
|
+
# Derive the REST base URL from the config URL
|
|
89
|
+
# Config URL may be wss:// for realtime; we need https:// for REST
|
|
90
|
+
raw_url = config.url.rstrip("/")
|
|
91
|
+
if raw_url.startswith("wss://"):
|
|
92
|
+
raw_url = raw_url.replace("wss://", "https://", 1)
|
|
93
|
+
elif raw_url.startswith("ws://"):
|
|
94
|
+
raw_url = raw_url.replace("ws://", "http://", 1)
|
|
95
|
+
self._base_url = raw_url
|
|
96
|
+
|
|
97
|
+
# Batch API path
|
|
98
|
+
self._batch_path = config.batch_path
|
|
99
|
+
|
|
100
|
+
# Session is lazily created
|
|
101
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
102
|
+
self._closed = False
|
|
103
|
+
|
|
104
|
+
logger.debug(
|
|
105
|
+
"BatchClient initialized (base_url=%s, batch_path=%s)",
|
|
106
|
+
self._base_url,
|
|
107
|
+
self._batch_path,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
# Async context manager
|
|
112
|
+
# ------------------------------------------------------------------
|
|
113
|
+
async def __aenter__(self) -> BatchClient:
|
|
114
|
+
"""Async context manager entry."""
|
|
115
|
+
await self._ensure_session()
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
119
|
+
"""Async context manager exit with automatic cleanup."""
|
|
120
|
+
await self.close()
|
|
121
|
+
|
|
122
|
+
# ------------------------------------------------------------------
|
|
123
|
+
# Public API
|
|
124
|
+
# ------------------------------------------------------------------
|
|
125
|
+
async def submit_job(
|
|
126
|
+
self,
|
|
127
|
+
presigned_url: Optional[str] = None,
|
|
128
|
+
audio_file: Optional[Union[str, Path, BinaryIO, bytes]] = None,
|
|
129
|
+
config: Optional[TranscriptionConfig] = None,
|
|
130
|
+
**kwargs: Any,
|
|
131
|
+
) -> JobDetails:
|
|
132
|
+
"""
|
|
133
|
+
Submit a transcription job.
|
|
134
|
+
|
|
135
|
+
Either presigned_url or audio_file must be provided, but not both.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
presigned_url: Presigned URL to audio file (S3, etc.)
|
|
139
|
+
audio_file: Audio file path, file-like object, or bytes.
|
|
140
|
+
config: TranscriptionConfig with transcription parameters.
|
|
141
|
+
**kwargs: Additional transcription parameters (language, model, etc.)
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
JobDetails object with job_id and initial status.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
BatchError: If job submission fails.
|
|
148
|
+
SpeechCortexApiKeyError: If API key is invalid.
|
|
149
|
+
ValueError: If neither or both of presigned_url / audio_file are provided.
|
|
150
|
+
"""
|
|
151
|
+
if presigned_url and audio_file:
|
|
152
|
+
raise ValueError("Cannot specify both presigned_url and audio_file")
|
|
153
|
+
if not presigned_url and not audio_file:
|
|
154
|
+
raise ValueError("Must provide either presigned_url or audio_file")
|
|
155
|
+
|
|
156
|
+
# Build transcription config
|
|
157
|
+
if config is None:
|
|
158
|
+
config = TranscriptionConfig()
|
|
159
|
+
|
|
160
|
+
# Override config with kwargs
|
|
161
|
+
for key, value in kwargs.items():
|
|
162
|
+
if hasattr(config, key):
|
|
163
|
+
setattr(config, key, value)
|
|
164
|
+
|
|
165
|
+
query_params = config.to_query_params()
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
if presigned_url:
|
|
169
|
+
return await self._submit_presigned_url(presigned_url, query_params)
|
|
170
|
+
else:
|
|
171
|
+
return await self._submit_file_upload(audio_file, query_params) # type: ignore[arg-type]
|
|
172
|
+
except SpeechCortexApiKeyError:
|
|
173
|
+
raise
|
|
174
|
+
except BatchError:
|
|
175
|
+
raise
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise BatchError(f"Failed to submit job: {e}") from e
|
|
178
|
+
|
|
179
|
+
async def get_status(self, job_id: Union[str, UUID]) -> TranscriptionStatus:
|
|
180
|
+
"""
|
|
181
|
+
Get the current status of a transcription job.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
job_id: Job identifier (UUID string or UUID object).
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
TranscriptionStatus object with current status.
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
JobNotFoundError: If job is not found.
|
|
191
|
+
BatchError: If request fails.
|
|
192
|
+
"""
|
|
193
|
+
job_id_str = str(job_id)
|
|
194
|
+
url = f"{self._base_url}{self._batch_path}/status/{job_id_str}"
|
|
195
|
+
|
|
196
|
+
logger.debug("Getting status for job: %s", job_id_str)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
data = await self._request("GET", url)
|
|
200
|
+
return TranscriptionStatus.from_dict(data)
|
|
201
|
+
except JobNotFoundError:
|
|
202
|
+
raise
|
|
203
|
+
except Exception as e:
|
|
204
|
+
raise BatchError(f"Failed to get status: {e}") from e
|
|
205
|
+
|
|
206
|
+
async def get_transcription(self, job_id: Union[str, UUID]) -> TranscriptionResult:
|
|
207
|
+
"""
|
|
208
|
+
Get the transcription result for a completed job.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
job_id: Job identifier (UUID string or UUID object).
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
TranscriptionResult object with transcription data.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
JobNotFoundError: If job is not found.
|
|
218
|
+
TranscriptionNotReadyError: If job is not completed yet.
|
|
219
|
+
JobFailedError: If job failed.
|
|
220
|
+
BatchError: If request fails.
|
|
221
|
+
"""
|
|
222
|
+
job_id_str = str(job_id)
|
|
223
|
+
url = f"{self._base_url}{self._batch_path}/get_transcription/{job_id_str}"
|
|
224
|
+
|
|
225
|
+
logger.debug("Getting transcription for job: %s", job_id_str)
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
data = await self._request("GET", url, allow_202=True)
|
|
229
|
+
|
|
230
|
+
# 202 means job is not ready yet (handled via status code in _request)
|
|
231
|
+
if data.get("_status_code") == 202:
|
|
232
|
+
status = data.get("status", "unknown")
|
|
233
|
+
raise TranscriptionNotReadyError(job_id_str, status)
|
|
234
|
+
|
|
235
|
+
# Check if job failed
|
|
236
|
+
if data.get("status", "").upper() == "FAILED":
|
|
237
|
+
error_msg = data.get("message", "Transcription failed")
|
|
238
|
+
raise JobFailedError(job_id_str, error_msg)
|
|
239
|
+
|
|
240
|
+
return TranscriptionResult.from_dict(data)
|
|
241
|
+
|
|
242
|
+
except (JobNotFoundError, TranscriptionNotReadyError, JobFailedError):
|
|
243
|
+
raise
|
|
244
|
+
except BatchError:
|
|
245
|
+
raise
|
|
246
|
+
except Exception as e:
|
|
247
|
+
raise BatchError(f"Failed to get transcription: {e}") from e
|
|
248
|
+
|
|
249
|
+
async def wait_for_completion(
|
|
250
|
+
self,
|
|
251
|
+
job_id: Union[str, UUID],
|
|
252
|
+
options: Optional[BatchOptions] = None,
|
|
253
|
+
**kwargs: Any,
|
|
254
|
+
) -> TranscriptionResult:
|
|
255
|
+
"""
|
|
256
|
+
Wait for a job to complete and return the result.
|
|
257
|
+
|
|
258
|
+
This method polls the job status at regular intervals until the job
|
|
259
|
+
completes or fails. Uses asyncio.wait_for for timeout handling.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
job_id: Job identifier (UUID string or UUID object).
|
|
263
|
+
options: BatchOptions with polling configuration.
|
|
264
|
+
**kwargs: Additional options (polling_interval, timeout).
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
TranscriptionResult object with transcription data.
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
BatchTimeoutError: If job doesn't complete within timeout.
|
|
271
|
+
JobFailedError: If job fails.
|
|
272
|
+
BatchError: If request fails.
|
|
273
|
+
"""
|
|
274
|
+
if options is None:
|
|
275
|
+
options = BatchOptions()
|
|
276
|
+
|
|
277
|
+
polling_interval = kwargs.get("polling_interval", options.polling_interval)
|
|
278
|
+
timeout = kwargs.get("timeout", options.timeout)
|
|
279
|
+
job_id_str = str(job_id)
|
|
280
|
+
|
|
281
|
+
logger.info(
|
|
282
|
+
"Waiting for job completion: %s (polling_interval=%.1fs, timeout=%s)",
|
|
283
|
+
job_id_str,
|
|
284
|
+
polling_interval,
|
|
285
|
+
timeout or "none",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
await asyncio.wait_for(
|
|
290
|
+
self._poll_job_status(job_id_str, polling_interval),
|
|
291
|
+
timeout=timeout,
|
|
292
|
+
)
|
|
293
|
+
return await self.get_transcription(job_id_str)
|
|
294
|
+
|
|
295
|
+
except asyncio.TimeoutError:
|
|
296
|
+
raise BatchTimeoutError(job_id_str, timeout) from None
|
|
297
|
+
|
|
298
|
+
async def transcribe(
|
|
299
|
+
self,
|
|
300
|
+
presigned_url: Optional[str] = None,
|
|
301
|
+
audio_file: Optional[Union[str, Path, BinaryIO, bytes]] = None,
|
|
302
|
+
config: Optional[TranscriptionConfig] = None,
|
|
303
|
+
options: Optional[BatchOptions] = None,
|
|
304
|
+
**kwargs: Any,
|
|
305
|
+
) -> TranscriptionResult:
|
|
306
|
+
"""
|
|
307
|
+
Convenience method: submit job and wait for completion.
|
|
308
|
+
|
|
309
|
+
Combines submit_job() and wait_for_completion() in a single call.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
presigned_url: Presigned URL to audio file.
|
|
313
|
+
audio_file: Audio file path, file-like object, or bytes.
|
|
314
|
+
config: TranscriptionConfig with transcription parameters.
|
|
315
|
+
options: BatchOptions with polling configuration.
|
|
316
|
+
**kwargs: Additional parameters (language, model, polling_interval, timeout, etc.)
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
TranscriptionResult object with transcription data.
|
|
320
|
+
|
|
321
|
+
Raises:
|
|
322
|
+
BatchError: If any step fails.
|
|
323
|
+
"""
|
|
324
|
+
# Extract polling options from kwargs
|
|
325
|
+
polling_kwargs: Dict[str, Any] = {}
|
|
326
|
+
for key in ("polling_interval", "timeout"):
|
|
327
|
+
if key in kwargs:
|
|
328
|
+
polling_kwargs[key] = kwargs.pop(key)
|
|
329
|
+
|
|
330
|
+
# Submit job
|
|
331
|
+
job = await self.submit_job(
|
|
332
|
+
presigned_url=presigned_url,
|
|
333
|
+
audio_file=audio_file,
|
|
334
|
+
config=config,
|
|
335
|
+
**kwargs,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
logger.debug("Waiting for job completion (job_id=%s)", job.job_id)
|
|
339
|
+
|
|
340
|
+
# Wait for completion
|
|
341
|
+
result = await self.wait_for_completion(
|
|
342
|
+
job.job_id,
|
|
343
|
+
options=options,
|
|
344
|
+
**polling_kwargs,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
logger.info("Transcription completed successfully (job_id=%s)", job.job_id)
|
|
348
|
+
return result
|
|
349
|
+
|
|
350
|
+
async def close(self) -> None:
|
|
351
|
+
"""
|
|
352
|
+
Close the HTTP session and cleanup resources.
|
|
353
|
+
|
|
354
|
+
Safe to call multiple times.
|
|
355
|
+
"""
|
|
356
|
+
if self._session and not self._closed:
|
|
357
|
+
try:
|
|
358
|
+
await self._session.close()
|
|
359
|
+
except Exception:
|
|
360
|
+
pass # best-effort cleanup
|
|
361
|
+
finally:
|
|
362
|
+
self._session = None
|
|
363
|
+
self._closed = True
|
|
364
|
+
logger.debug("BatchClient session closed")
|
|
365
|
+
|
|
366
|
+
# ------------------------------------------------------------------
|
|
367
|
+
# Internal helpers
|
|
368
|
+
# ------------------------------------------------------------------
|
|
369
|
+
async def _ensure_session(self) -> None:
|
|
370
|
+
"""Create aiohttp session lazily."""
|
|
371
|
+
if self._session is None and not self._closed:
|
|
372
|
+
timeout = aiohttp.ClientTimeout(total=300.0, connect=30.0)
|
|
373
|
+
self._session = aiohttp.ClientSession(timeout=timeout)
|
|
374
|
+
logger.debug("aiohttp session created")
|
|
375
|
+
|
|
376
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
377
|
+
"""Build common request headers."""
|
|
378
|
+
return {
|
|
379
|
+
"X-API-Key": self._api_key,
|
|
380
|
+
"User-Agent": (
|
|
381
|
+
f"speechcortex-sdk python/{sys.version_info.major}.{sys.version_info.minor}"
|
|
382
|
+
),
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async def _request(
|
|
386
|
+
self,
|
|
387
|
+
method: str,
|
|
388
|
+
url: str,
|
|
389
|
+
*,
|
|
390
|
+
params: Optional[Dict[str, Any]] = None,
|
|
391
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
392
|
+
form_data: Optional[aiohttp.FormData] = None,
|
|
393
|
+
allow_202: bool = False,
|
|
394
|
+
) -> Dict[str, Any]:
|
|
395
|
+
"""
|
|
396
|
+
Send an HTTP request and return JSON response.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
method: HTTP method (GET, POST).
|
|
400
|
+
url: Full URL.
|
|
401
|
+
params: Query parameters.
|
|
402
|
+
json_data: JSON body.
|
|
403
|
+
form_data: Multipart form data.
|
|
404
|
+
allow_202: If True, return 202 responses with _status_code marker.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
JSON response as dict.
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
SpeechCortexApiKeyError: On 401.
|
|
411
|
+
JobNotFoundError: On 404.
|
|
412
|
+
BatchError: On other errors.
|
|
413
|
+
"""
|
|
414
|
+
await self._ensure_session()
|
|
415
|
+
assert self._session is not None
|
|
416
|
+
|
|
417
|
+
headers = self._get_headers()
|
|
418
|
+
|
|
419
|
+
kwargs: Dict[str, Any] = {"headers": headers, "params": params}
|
|
420
|
+
if json_data is not None:
|
|
421
|
+
kwargs["json"] = json_data
|
|
422
|
+
if form_data is not None:
|
|
423
|
+
# Don't set Content-Type for multipart — aiohttp handles it
|
|
424
|
+
kwargs["data"] = form_data
|
|
425
|
+
|
|
426
|
+
logger.debug("HTTP %s %s", method, url)
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
async with self._session.request(method, url, **kwargs) as response:
|
|
430
|
+
if response.status == 401:
|
|
431
|
+
raise SpeechCortexApiKeyError("Invalid API key")
|
|
432
|
+
|
|
433
|
+
if response.status == 404:
|
|
434
|
+
raise JobNotFoundError("unknown", "Resource not found")
|
|
435
|
+
|
|
436
|
+
if response.status == 202 and allow_202:
|
|
437
|
+
data = await response.json()
|
|
438
|
+
data["_status_code"] = 202
|
|
439
|
+
return data
|
|
440
|
+
|
|
441
|
+
if response.status >= 400:
|
|
442
|
+
error_text = await response.text()
|
|
443
|
+
logger.error("HTTP %d: %s", response.status, error_text)
|
|
444
|
+
raise BatchError(f"HTTP {response.status}: {error_text}")
|
|
445
|
+
|
|
446
|
+
return await response.json()
|
|
447
|
+
|
|
448
|
+
except (SpeechCortexApiKeyError, JobNotFoundError, BatchError):
|
|
449
|
+
raise
|
|
450
|
+
except aiohttp.ClientError as e:
|
|
451
|
+
raise BatchError(f"Request failed: {e}") from e
|
|
452
|
+
except asyncio.TimeoutError:
|
|
453
|
+
raise BatchError(f"Request timeout: {method} {url}") from None
|
|
454
|
+
except Exception as e:
|
|
455
|
+
raise BatchError(f"Unexpected error: {e}") from e
|
|
456
|
+
|
|
457
|
+
async def _submit_presigned_url(
|
|
458
|
+
self, presigned_url: str, query_params: Dict[str, Any]
|
|
459
|
+
) -> JobDetails:
|
|
460
|
+
"""Submit job with presigned URL."""
|
|
461
|
+
url = f"{self._base_url}{self._batch_path}/transcribe"
|
|
462
|
+
|
|
463
|
+
logger.debug("Submitting job with presigned URL: %s", url)
|
|
464
|
+
|
|
465
|
+
data = await self._request(
|
|
466
|
+
"POST",
|
|
467
|
+
url,
|
|
468
|
+
params=query_params,
|
|
469
|
+
json_data={"presigned_url": presigned_url},
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if "job_id" not in data:
|
|
473
|
+
error_msg = data.get("message", "No job_id in response")
|
|
474
|
+
raise BatchError(f"Job submission failed: {error_msg}")
|
|
475
|
+
|
|
476
|
+
logger.info("Job submitted successfully: %s", data.get("job_id"))
|
|
477
|
+
return JobDetails.from_dict(data)
|
|
478
|
+
|
|
479
|
+
async def _submit_file_upload(
|
|
480
|
+
self,
|
|
481
|
+
audio_file: Union[str, Path, BinaryIO, bytes],
|
|
482
|
+
query_params: Dict[str, Any],
|
|
483
|
+
) -> JobDetails:
|
|
484
|
+
"""Submit job with file upload."""
|
|
485
|
+
url = f"{self._base_url}{self._batch_path}/transcribe/upload"
|
|
486
|
+
|
|
487
|
+
logger.debug("Submitting job with file upload: %s", url)
|
|
488
|
+
|
|
489
|
+
form = aiohttp.FormData()
|
|
490
|
+
|
|
491
|
+
if isinstance(audio_file, (str, Path)):
|
|
492
|
+
file_path = Path(audio_file)
|
|
493
|
+
if not file_path.exists():
|
|
494
|
+
raise FileNotFoundError(f"Audio file not found: {audio_file}")
|
|
495
|
+
|
|
496
|
+
async with aiofiles.open(file_path, "rb") as f:
|
|
497
|
+
file_content = await f.read()
|
|
498
|
+
form.add_field(
|
|
499
|
+
"audio_file",
|
|
500
|
+
file_content,
|
|
501
|
+
filename=file_path.name,
|
|
502
|
+
content_type="audio/mpeg",
|
|
503
|
+
)
|
|
504
|
+
elif isinstance(audio_file, bytes):
|
|
505
|
+
form.add_field(
|
|
506
|
+
"audio_file",
|
|
507
|
+
audio_file,
|
|
508
|
+
filename="audio.mp3",
|
|
509
|
+
content_type="audio/mpeg",
|
|
510
|
+
)
|
|
511
|
+
else:
|
|
512
|
+
# File-like object
|
|
513
|
+
content = audio_file.read()
|
|
514
|
+
filename = getattr(audio_file, "name", "audio.mp3")
|
|
515
|
+
if hasattr(filename, "split"):
|
|
516
|
+
filename = os.path.basename(filename)
|
|
517
|
+
form.add_field(
|
|
518
|
+
"audio_file",
|
|
519
|
+
content,
|
|
520
|
+
filename=filename,
|
|
521
|
+
content_type="audio/mpeg",
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
data = await self._request(
|
|
525
|
+
"POST",
|
|
526
|
+
url,
|
|
527
|
+
params=query_params,
|
|
528
|
+
form_data=form,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
if "job_id" not in data:
|
|
532
|
+
error_msg = data.get("message", "No job_id in response")
|
|
533
|
+
raise BatchError(f"Job submission failed: {error_msg}")
|
|
534
|
+
|
|
535
|
+
logger.info("Job submitted successfully: %s", data.get("job_id"))
|
|
536
|
+
return JobDetails.from_dict(data)
|
|
537
|
+
|
|
538
|
+
async def _poll_job_status(self, job_id: str, polling_interval: float) -> None:
|
|
539
|
+
"""Poll job status until completion or failure."""
|
|
540
|
+
logger.debug(
|
|
541
|
+
"Starting status polling for job_id=%s (interval=%.1fs)",
|
|
542
|
+
job_id,
|
|
543
|
+
polling_interval,
|
|
544
|
+
)
|
|
545
|
+
poll_count = 0
|
|
546
|
+
|
|
547
|
+
while True:
|
|
548
|
+
poll_count += 1
|
|
549
|
+
status = await self.get_status(job_id)
|
|
550
|
+
status_upper = status.status.upper()
|
|
551
|
+
|
|
552
|
+
logger.debug(
|
|
553
|
+
"Job %s status: %s (poll #%d)", job_id, status_upper, poll_count
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
if status_upper == "COMPLETED":
|
|
557
|
+
logger.info("Job completed (job_id=%s, polls=%d)", job_id, poll_count)
|
|
558
|
+
return
|
|
559
|
+
|
|
560
|
+
if status_upper == "FAILED":
|
|
561
|
+
error_msg = status.error_message or "Job failed"
|
|
562
|
+
raise JobFailedError(job_id, error_msg)
|
|
563
|
+
|
|
564
|
+
await asyncio.sleep(polling_interval)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright 2024 SpeechCortex SDK contributors. All Rights Reserved.
|
|
2
|
+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Batch transcription specific exceptions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ....errors import SpeechCortexError, SpeechCortexApiError, SpeechCortexTimeoutError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BatchError(SpeechCortexError):
|
|
13
|
+
"""
|
|
14
|
+
Base exception for all batch transcription errors.
|
|
15
|
+
"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class JobNotFoundError(BatchError):
|
|
20
|
+
"""
|
|
21
|
+
Exception raised when a transcription job is not found.
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self, job_id: str, message: str = None):
|
|
24
|
+
msg = message or f"Job not found: {job_id}"
|
|
25
|
+
super().__init__(msg)
|
|
26
|
+
self.job_id = job_id
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class JobFailedError(BatchError):
|
|
30
|
+
"""
|
|
31
|
+
Exception raised when a transcription job fails.
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, job_id: str, error_message: str = None):
|
|
34
|
+
msg = error_message or f"Job {job_id} failed"
|
|
35
|
+
super().__init__(msg)
|
|
36
|
+
self.job_id = job_id
|
|
37
|
+
self.error_message = error_message
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TranscriptionNotReadyError(BatchError):
|
|
41
|
+
"""
|
|
42
|
+
Exception raised when attempting to get transcription result before job is completed.
|
|
43
|
+
"""
|
|
44
|
+
def __init__(self, job_id: str, status: str):
|
|
45
|
+
msg = f"Transcription not ready for job {job_id}. Current status: {status}"
|
|
46
|
+
super().__init__(msg)
|
|
47
|
+
self.job_id = job_id
|
|
48
|
+
self.status = status
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BatchTimeoutError(SpeechCortexTimeoutError):
|
|
52
|
+
"""
|
|
53
|
+
Exception raised when waiting for job completion times out.
|
|
54
|
+
"""
|
|
55
|
+
def __init__(self, job_id: str, timeout: float):
|
|
56
|
+
msg = f"Job {job_id} did not complete within {timeout} seconds"
|
|
57
|
+
super().__init__(msg)
|
|
58
|
+
self.job_id = job_id
|
|
59
|
+
self.timeout = timeout
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copyright 2024 SpeechCortex SDK contributors. All Rights Reserved.
|
|
2
|
+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Configuration options for batch transcription.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Optional, Dict, Any, Union
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class TranscriptionConfig:
|
|
16
|
+
"""
|
|
17
|
+
Configuration for transcription parameters.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
language: Language code (e.g., "en-US", "ENGLISH")
|
|
21
|
+
model: Transcription model to use (default: "batch-zeus")
|
|
22
|
+
diarize: Enable speaker diarization (default: False)
|
|
23
|
+
punctuate: Enable punctuation (default: True)
|
|
24
|
+
smart_format: Enable smart formatting (default: True)
|
|
25
|
+
channel: Number of audio channels (default: 2)
|
|
26
|
+
pci: PCI compliance flag (default: False)
|
|
27
|
+
extra_params: Additional parameters as dict or JSON string
|
|
28
|
+
"""
|
|
29
|
+
language: str = "en-US"
|
|
30
|
+
model: str = "batch-zeus"
|
|
31
|
+
diarize: bool = False
|
|
32
|
+
punctuate: bool = False
|
|
33
|
+
smart_format: bool = False
|
|
34
|
+
channel: int = 2
|
|
35
|
+
pci: bool = False
|
|
36
|
+
extra_params: Optional[Union[Dict[str, Any], str]] = None
|
|
37
|
+
|
|
38
|
+
def to_query_params(self) -> Dict[str, Any]:
|
|
39
|
+
"""Convert config to query parameters for API request."""
|
|
40
|
+
params = {
|
|
41
|
+
"language": self.language,
|
|
42
|
+
"model": self.model,
|
|
43
|
+
"diarize": str(self.diarize).lower(),
|
|
44
|
+
"punctuate": str(self.punctuate).lower(),
|
|
45
|
+
"smart_format": str(self.smart_format).lower(),
|
|
46
|
+
"channel": str(self.channel),
|
|
47
|
+
"pci": str(self.pci).lower(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Handle extra_params
|
|
51
|
+
if self.extra_params:
|
|
52
|
+
if isinstance(self.extra_params, str):
|
|
53
|
+
import json
|
|
54
|
+
try:
|
|
55
|
+
extra = json.loads(self.extra_params)
|
|
56
|
+
if isinstance(extra, dict):
|
|
57
|
+
params.update(extra)
|
|
58
|
+
except json.JSONDecodeError:
|
|
59
|
+
pass
|
|
60
|
+
elif isinstance(self.extra_params, dict):
|
|
61
|
+
params.update(self.extra_params)
|
|
62
|
+
|
|
63
|
+
return params
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class BatchOptions:
|
|
68
|
+
"""
|
|
69
|
+
Options for batch transcription operations.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
polling_interval: Time in seconds between status checks (default: 3.0)
|
|
73
|
+
timeout: Maximum time in seconds to wait for completion (default: None, no timeout)
|
|
74
|
+
batch_path: API path for batch transcription endpoints (default: "/api/v1/transcription")
|
|
75
|
+
"""
|
|
76
|
+
polling_interval: float = 3.0
|
|
77
|
+
timeout: Optional[float] = None
|
|
78
|
+
batch_path: str = field(default_factory=lambda: os.getenv(
|
|
79
|
+
"SPEECHCORTEX_BATCH_PATH",
|
|
80
|
+
"/api/v1/transcription"
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
def __post_init__(self):
|
|
84
|
+
"""Validate options after initialization."""
|
|
85
|
+
if self.polling_interval <= 0:
|
|
86
|
+
raise ValueError("polling_interval must be greater than 0")
|
|
87
|
+
if self.timeout is not None and self.timeout <= 0:
|
|
88
|
+
raise ValueError("timeout must be greater than 0")
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Copyright 2024 SpeechCortex SDK contributors. All Rights Reserved.
|
|
2
|
+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Response models for batch transcription.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Optional, Dict, Any
|
|
12
|
+
from uuid import UUID
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class JobDetails:
|
|
17
|
+
"""
|
|
18
|
+
Details about a transcription job.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
job_id: Unique job identifier
|
|
22
|
+
status: Current job status (pending, downloading, uploading, queued, processing, completed, failed)
|
|
23
|
+
created_at: Job creation timestamp
|
|
24
|
+
updated_at: Last update timestamp (optional)
|
|
25
|
+
error_message: Error message if job failed (optional)
|
|
26
|
+
"""
|
|
27
|
+
job_id: UUID
|
|
28
|
+
status: str
|
|
29
|
+
created_at: datetime
|
|
30
|
+
updated_at: Optional[datetime] = None
|
|
31
|
+
error_message: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_dict(cls, data: Dict[str, Any]) -> "JobDetails":
|
|
35
|
+
"""Create JobDetails from API response dictionary."""
|
|
36
|
+
# Handle both UUID string and UUID object
|
|
37
|
+
job_id = data.get("job_id")
|
|
38
|
+
if isinstance(job_id, str):
|
|
39
|
+
job_id = UUID(job_id)
|
|
40
|
+
elif not isinstance(job_id, UUID):
|
|
41
|
+
raise ValueError(f"Invalid job_id format: {job_id}")
|
|
42
|
+
|
|
43
|
+
# Parse timestamps
|
|
44
|
+
created_at = data.get("created_at")
|
|
45
|
+
if isinstance(created_at, str):
|
|
46
|
+
# Handle ISO format with or without timezone
|
|
47
|
+
created_at = created_at.replace("Z", "+00:00")
|
|
48
|
+
created_at = datetime.fromisoformat(created_at)
|
|
49
|
+
elif not isinstance(created_at, datetime):
|
|
50
|
+
created_at = datetime.now()
|
|
51
|
+
|
|
52
|
+
updated_at = data.get("updated_at")
|
|
53
|
+
if updated_at:
|
|
54
|
+
if isinstance(updated_at, str):
|
|
55
|
+
updated_at = updated_at.replace("Z", "+00:00")
|
|
56
|
+
updated_at = datetime.fromisoformat(updated_at)
|
|
57
|
+
elif not isinstance(updated_at, datetime):
|
|
58
|
+
updated_at = None
|
|
59
|
+
|
|
60
|
+
return cls(
|
|
61
|
+
job_id=job_id,
|
|
62
|
+
status=data.get("status", "pending"),
|
|
63
|
+
created_at=created_at,
|
|
64
|
+
updated_at=updated_at,
|
|
65
|
+
error_message=data.get("error_message"),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class TranscriptionStatus:
|
|
71
|
+
"""
|
|
72
|
+
Status information for a transcription job.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
job_id: Unique job identifier
|
|
76
|
+
status: Current job status
|
|
77
|
+
created_at: Job creation timestamp
|
|
78
|
+
updated_at: Last update timestamp (optional)
|
|
79
|
+
error_message: Error message if job failed (optional)
|
|
80
|
+
"""
|
|
81
|
+
job_id: UUID
|
|
82
|
+
status: str
|
|
83
|
+
created_at: Optional[datetime] = None
|
|
84
|
+
updated_at: Optional[datetime] = None
|
|
85
|
+
error_message: Optional[str] = None
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TranscriptionStatus":
|
|
89
|
+
"""Create TranscriptionStatus from API response dictionary."""
|
|
90
|
+
job_id = data.get("job_id")
|
|
91
|
+
if isinstance(job_id, str):
|
|
92
|
+
job_id = UUID(job_id)
|
|
93
|
+
elif not isinstance(job_id, UUID):
|
|
94
|
+
raise ValueError(f"Invalid job_id format: {job_id}")
|
|
95
|
+
|
|
96
|
+
created_at = None
|
|
97
|
+
if data.get("created_at"):
|
|
98
|
+
created_at_str = data["created_at"]
|
|
99
|
+
if isinstance(created_at_str, str):
|
|
100
|
+
created_at_str = created_at_str.replace("Z", "+00:00")
|
|
101
|
+
created_at = datetime.fromisoformat(created_at_str)
|
|
102
|
+
|
|
103
|
+
updated_at = None
|
|
104
|
+
if data.get("updated_at"):
|
|
105
|
+
updated_at_str = data["updated_at"]
|
|
106
|
+
if isinstance(updated_at_str, str):
|
|
107
|
+
updated_at_str = updated_at_str.replace("Z", "+00:00")
|
|
108
|
+
updated_at = datetime.fromisoformat(updated_at_str)
|
|
109
|
+
|
|
110
|
+
return cls(
|
|
111
|
+
job_id=job_id,
|
|
112
|
+
status=data.get("status", "pending"),
|
|
113
|
+
created_at=created_at,
|
|
114
|
+
updated_at=updated_at,
|
|
115
|
+
error_message=data.get("error_message"),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class TranscriptionResult:
|
|
121
|
+
"""
|
|
122
|
+
Transcription result for a completed job.
|
|
123
|
+
|
|
124
|
+
Attributes:
|
|
125
|
+
job_id: Unique job identifier
|
|
126
|
+
status: Job status
|
|
127
|
+
transcription: Transcription data (dict) - only present if completed
|
|
128
|
+
message: Status message
|
|
129
|
+
"""
|
|
130
|
+
job_id: UUID
|
|
131
|
+
status: str
|
|
132
|
+
transcription: Optional[Dict[str, Any]] = None
|
|
133
|
+
message: Optional[str] = None
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TranscriptionResult":
|
|
137
|
+
"""Create TranscriptionResult from API response dictionary."""
|
|
138
|
+
job_id = data.get("job_id")
|
|
139
|
+
if isinstance(job_id, str):
|
|
140
|
+
job_id = UUID(job_id)
|
|
141
|
+
elif not isinstance(job_id, UUID):
|
|
142
|
+
raise ValueError(f"Invalid job_id format: {job_id}")
|
|
143
|
+
|
|
144
|
+
return cls(
|
|
145
|
+
job_id=job_id,
|
|
146
|
+
status=data.get("status", "pending"),
|
|
147
|
+
transcription=data.get("transcription"),
|
|
148
|
+
message=data.get("message"),
|
|
149
|
+
)
|
|
@@ -15,7 +15,7 @@ from .errors import SpeechCortexApiKeyError
|
|
|
15
15
|
try:
|
|
16
16
|
from . import __version__
|
|
17
17
|
except ImportError:
|
|
18
|
-
__version__ = "0.1.
|
|
18
|
+
__version__ = "0.1.1"
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class SpeechCortexClientOptions: # pylint: disable=too-many-instance-attributes
|
|
@@ -44,6 +44,7 @@ class SpeechCortexClientOptions: # pylint: disable=too-many-instance-attributes
|
|
|
44
44
|
headers: Optional[Dict] = None,
|
|
45
45
|
options: Optional[Dict] = None,
|
|
46
46
|
realtime_path: str = "/transcribe/realtime",
|
|
47
|
+
batch_path: str = "/api/v1/transcription",
|
|
47
48
|
):
|
|
48
49
|
self._logger = verboselogs.VerboseLogger(__name__)
|
|
49
50
|
self._logger.addHandler(logging.StreamHandler())
|
|
@@ -114,6 +115,7 @@ class SpeechCortexClientOptions: # pylint: disable=too-many-instance-attributes
|
|
|
114
115
|
break
|
|
115
116
|
|
|
116
117
|
self.options = options
|
|
118
|
+
self.batch_path = batch_path
|
|
117
119
|
|
|
118
120
|
# Normalize realtime path (ensure starts with '/'; strip trailing '?')
|
|
119
121
|
if realtime_path is None or realtime_path == "":
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: speechcortex-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev2
|
|
4
4
|
Summary: The official Python SDK for SpeechCortex ASR platform.
|
|
5
|
-
Home-page: https://github.com/
|
|
5
|
+
Home-page: https://github.com/speechcortex/speechcortex-sdk
|
|
6
6
|
Author: SpeechCortex Team
|
|
7
7
|
Author-email: SpeechCortex Team <team@speechcortex.com>
|
|
8
8
|
License: MIT
|
|
9
|
-
Project-URL: Homepage, https://github.com/
|
|
10
|
-
Project-URL: Bug Tracker, https://github.com/
|
|
11
|
-
Project-URL: Source Code, https://github.com/
|
|
12
|
-
Project-URL: Documentation, https://github.com/
|
|
9
|
+
Project-URL: Homepage, https://github.com/speechcortex/speechcortex-sdk
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/speechcortex/speechcortex-sdk/issues
|
|
11
|
+
Project-URL: Source Code, https://github.com/speechcortex/speechcortex-sdk
|
|
12
|
+
Project-URL: Documentation, https://github.com/speechcortex/speechcortex-sdk#readme
|
|
13
13
|
Keywords: speechcortex,asr,speech-to-text,speech recognition
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -35,7 +35,7 @@ Dynamic: requires-python
|
|
|
35
35
|
|
|
36
36
|
# SpeechCortex Python SDK
|
|
37
37
|
|
|
38
|
-
[](https://github.com/speechcortex/speechcortex-sdk/actions/workflows/ci.yml)
|
|
39
39
|
[](https://badge.fury.io/py/speechcortex-sdk)
|
|
40
40
|
[](https://pypi.org/project/speechcortex-sdk/)
|
|
41
41
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -57,7 +57,7 @@ Official Python SDK for SpeechCortex ASR (Automatic Speech Recognition) platform
|
|
|
57
57
|
## Installation
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
pip install "git+https://github.com/
|
|
60
|
+
pip install "git+https://github.com/speechcortex/speechcortex-sdk.git@package_init"
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
```bash
|
|
@@ -17,6 +17,11 @@ speechcortex/audio/microphone/microphone.py
|
|
|
17
17
|
speechcortex/clients/__init__.py
|
|
18
18
|
speechcortex/clients/transcribe/__init__.py
|
|
19
19
|
speechcortex/clients/transcribe/enums.py
|
|
20
|
+
speechcortex/clients/transcribe/batch/__init__.py
|
|
21
|
+
speechcortex/clients/transcribe/batch/client.py
|
|
22
|
+
speechcortex/clients/transcribe/batch/exceptions.py
|
|
23
|
+
speechcortex/clients/transcribe/batch/options.py
|
|
24
|
+
speechcortex/clients/transcribe/batch/response.py
|
|
20
25
|
speechcortex/clients/transcribe/realtime/__init__.py
|
|
21
26
|
speechcortex/clients/transcribe/realtime/client.py
|
|
22
27
|
speechcortex/clients/transcribe/realtime/options.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/__init__.py
RENAMED
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/constants.py
RENAMED
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/errors.py
RENAMED
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/audio/microphone/microphone.py
RENAMED
|
File without changes
|
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/clients/transcribe/enums.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex/utils/verboselogs/__init__.py
RENAMED
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{speechcortex_sdk-0.1.0 → speechcortex_sdk-0.1.dev2}/speechcortex_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|