runapi-openai-tts 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.
- runapi_openai_tts-0.1.0/PKG-INFO +24 -0
- runapi_openai_tts-0.1.0/README.md +7 -0
- runapi_openai_tts-0.1.0/pyproject.toml +34 -0
- runapi_openai_tts-0.1.0/src/runapi/openai_tts/__init__.py +20 -0
- runapi_openai_tts-0.1.0/src/runapi/openai_tts/client.py +19 -0
- runapi_openai_tts-0.1.0/src/runapi/openai_tts/contract_gen.py +21 -0
- runapi_openai_tts-0.1.0/src/runapi/openai_tts/py.typed +1 -0
- runapi_openai_tts-0.1.0/src/runapi/openai_tts/resources/__init__.py +3 -0
- runapi_openai_tts-0.1.0/src/runapi/openai_tts/resources/text_to_speech.py +22 -0
- runapi_openai_tts-0.1.0/src/runapi/openai_tts/types.py +21 -0
- runapi_openai_tts-0.1.0/tests/test_client.py +39 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runapi-openai-tts
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RunAPI OpenAI TTS SDK for speech generation in JavaScript, Python, Ruby, Go, Java, and PHP
|
|
5
|
+
Project-URL: Homepage, https://runapi.ai/models/openai-tts
|
|
6
|
+
Project-URL: Documentation, https://runapi.ai/docs#sdk-openai-tts
|
|
7
|
+
Project-URL: Source, https://github.com/runapi-ai/openai-tts-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/runapi-ai/openai-tts-sdk/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/runapi-ai/openai-tts-sdk/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Release Notes, https://github.com/runapi-ai/openai-tts-sdk/releases/tag/python%2Fv0.1.0
|
|
11
|
+
Author-email: RunAPI <contact@runapi.ai>
|
|
12
|
+
License-Expression: Apache-2.0
|
|
13
|
+
Keywords: api,audio,golang,java,maven,openai,openai-tts,python,ruby,runapi,runapi-ai,sdk,speech,tts,typescript
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Requires-Dist: runapi-core>=0.2.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# OpenAI TTS Python SDK for RunAPI
|
|
19
|
+
|
|
20
|
+
Install `runapi-openai-tts`, create `OpenaiTtsClient`, and call `client.text_to_speech.run(model="tts-1", text="Hello")`.
|
|
21
|
+
|
|
22
|
+
Model details and pricing: https://runapi.ai/models/openai-tts
|
|
23
|
+
|
|
24
|
+
Licensed under the Apache License, Version 2.0.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# OpenAI TTS Python SDK for RunAPI
|
|
2
|
+
|
|
3
|
+
Install `runapi-openai-tts`, create `OpenaiTtsClient`, and call `client.text_to_speech.run(model="tts-1", text="Hello")`.
|
|
4
|
+
|
|
5
|
+
Model details and pricing: https://runapi.ai/models/openai-tts
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runapi-openai-tts"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "RunAPI OpenAI TTS SDK for speech generation in JavaScript, Python, Ruby, Go, Java, and PHP"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
|
|
13
|
+
keywords = ["runapi", "runapi-ai", "openai", "openai-tts", "tts", "speech", "audio", "api", "sdk", "typescript", "python", "ruby", "golang", "java", "maven"]
|
|
14
|
+
dependencies = ["runapi-core>=0.2.0"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://runapi.ai/models/openai-tts"
|
|
18
|
+
Documentation = "https://runapi.ai/docs#sdk-openai-tts"
|
|
19
|
+
Source = "https://github.com/runapi-ai/openai-tts-sdk"
|
|
20
|
+
Issues = "https://github.com/runapi-ai/openai-tts-sdk/issues"
|
|
21
|
+
Changelog = "https://github.com/runapi-ai/openai-tts-sdk/blob/main/CHANGELOG.md"
|
|
22
|
+
"Release Notes" = "https://github.com/runapi-ai/openai-tts-sdk/releases/tag/python%2Fv0.1.0"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/runapi"]
|
|
26
|
+
|
|
27
|
+
[tool.uv]
|
|
28
|
+
package = true
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = ["pytest>=8"]
|
|
32
|
+
|
|
33
|
+
[tool.runapi]
|
|
34
|
+
slug = "openai-tts"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""OpenAI TTS client for RunAPI."""
|
|
2
|
+
|
|
3
|
+
from runapi.core import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
InsufficientCreditsError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
RateLimitError,
|
|
8
|
+
ValidationError,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .client import OpenaiTtsClient
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"OpenaiTtsClient",
|
|
15
|
+
"AuthenticationError",
|
|
16
|
+
"RateLimitError",
|
|
17
|
+
"InsufficientCreditsError",
|
|
18
|
+
"NotFoundError",
|
|
19
|
+
"ValidationError",
|
|
20
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""OpenAI TTS client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from runapi.core import ClientOptions, HttpClient, resolve_api_key
|
|
8
|
+
|
|
9
|
+
from .resources.text_to_speech import TextToSpeech
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenaiTtsClient:
|
|
13
|
+
"""OpenAI TTS speech generation client."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
|
|
16
|
+
resolved_api_key = resolve_api_key(api_key)
|
|
17
|
+
client_options = ClientOptions(api_key=resolved_api_key, **options)
|
|
18
|
+
http = client_options.http_client or HttpClient(client_options)
|
|
19
|
+
self.text_to_speech = TextToSpeech(http)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
CONTRACT = {
|
|
2
|
+
"text-to-speech": {
|
|
3
|
+
"models": ["tts-1", "tts-1-hd"],
|
|
4
|
+
"fields_by_model": {
|
|
5
|
+
"tts-1": {
|
|
6
|
+
"text": {
|
|
7
|
+
"required": True,
|
|
8
|
+
"max": 4096,
|
|
9
|
+
"length": True
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"tts-1-hd": {
|
|
13
|
+
"text": {
|
|
14
|
+
"required": True,
|
|
15
|
+
"max": 4096,
|
|
16
|
+
"length": True
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""OpenAI TTS text-to-speech resource."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from runapi.core import RequestOptions, Resource
|
|
8
|
+
|
|
9
|
+
from ..contract_gen import CONTRACT
|
|
10
|
+
from ..types import TextToSpeechResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TextToSpeech(Resource):
|
|
14
|
+
"""Generate a RunAPI-managed MP3 from text."""
|
|
15
|
+
|
|
16
|
+
ENDPOINT = "/api/v1/openai_tts/text_to_speech"
|
|
17
|
+
RESPONSE_CLASS = TextToSpeechResponse
|
|
18
|
+
|
|
19
|
+
def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
20
|
+
compacted = self._compact_params(params)
|
|
21
|
+
self._validate_contract(CONTRACT["text-to-speech"], compacted)
|
|
22
|
+
return self._request("post", self.ENDPOINT, body=compacted, options=options)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""OpenAI TTS response models."""
|
|
2
|
+
|
|
3
|
+
from runapi.core import BaseModel, optional, required
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Audio(BaseModel):
|
|
7
|
+
"""A RunAPI-managed MP3 audio result."""
|
|
8
|
+
|
|
9
|
+
url = required(str)
|
|
10
|
+
format = required(str)
|
|
11
|
+
mime_type = required(str)
|
|
12
|
+
size_bytes = required(int)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TextToSpeechResponse(BaseModel):
|
|
16
|
+
"""Completed synchronous text-to-speech response."""
|
|
17
|
+
|
|
18
|
+
id = required(str)
|
|
19
|
+
status = required(str)
|
|
20
|
+
audios = required([lambda: Audio])
|
|
21
|
+
error = optional(str)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from runapi.core import config
|
|
4
|
+
from runapi.core.errors import ValidationError
|
|
5
|
+
from runapi.openai_tts import OpenaiTtsClient
|
|
6
|
+
from runapi.openai_tts.types import TextToSpeechResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FakeHttp:
|
|
10
|
+
def __init__(self, *responses):
|
|
11
|
+
self._responses = list(responses)
|
|
12
|
+
self.calls = []
|
|
13
|
+
|
|
14
|
+
def request(self, method, path, body=None, options=None):
|
|
15
|
+
self.calls.append((method, path, body))
|
|
16
|
+
return self._responses.pop(0)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture(autouse=True)
|
|
20
|
+
def reset_config(monkeypatch):
|
|
21
|
+
monkeypatch.delenv("RUNAPI_API_KEY", raising=False)
|
|
22
|
+
monkeypatch.setattr(config, "api_key", None)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_run_posts_params_and_decodes_managed_audio():
|
|
26
|
+
fake = FakeHttp({"id": "task_1", "status": "completed", "audios": [{"url": "https://runapi.ai/audio.mp3", "format": "mp3", "mime_type": "audio/mpeg", "size_bytes": 128}]})
|
|
27
|
+
client = OpenaiTtsClient(api_key="k", http_client=fake)
|
|
28
|
+
|
|
29
|
+
result = client.text_to_speech.run(model="tts-1", text="Hello")
|
|
30
|
+
|
|
31
|
+
assert fake.calls == [("post", "/api/v1/openai_tts/text_to_speech", {"model": "tts-1", "text": "Hello"})]
|
|
32
|
+
assert isinstance(result, TextToSpeechResponse)
|
|
33
|
+
assert result.audios[0].mime_type == "audio/mpeg"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_run_requires_text():
|
|
37
|
+
client = OpenaiTtsClient(api_key="k", http_client=FakeHttp())
|
|
38
|
+
with pytest.raises(ValidationError, match="text is required"):
|
|
39
|
+
client.text_to_speech.run(model="tts-1")
|