Metadata-Version: 2.4
Name: hudsonly-ai
Version: 0.1.0
Summary: Official Python SDK for the Hudsonly AI API
Author-email: Hudsonly <support@hudsonly.com>
License-Expression: MIT
Project-URL: Homepage, https://ai.hudsonly.com
Project-URL: Documentation, https://ai.hudsonly.com/docs
Project-URL: Repository, https://gitlab.com/hudsonly/hudsonly-python
Keywords: hudsonly,ai,tts,transcription,embeddings,avatar
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# Hudsonly AI Python SDK

Official Python SDK for the [Hudsonly AI API](https://ai.hudsonly.com) — Text-to-Speech, Speech-to-Text, Text Embeddings, and Avatar Video Generation.

## Installation

```bash
pip install hudsonly
```

## Quick Start

```python
from hudsonly import HudsonlyAI

client = HudsonlyAI(api_key="your-api-token")

# Text to Speech
result = client.tts.generate(text="Hello world", voice="af_heart")
print(result.audio_url)

# Speech to Text
result = client.transcription.generate(audio="recording.mp3")
print(result.text)

# Text Embeddings
result = client.embeddings.generate(texts=["Hello", "World"])
print(result.embeddings)

# Avatar Video (auto-polls until complete)
result = client.avatar.generate(
    image="photo.jpg",
    audio="speech.mp3",
    model="sadtalker",
)
print(result.video_url)
```

## Services

### Text to Speech

Convert text to natural-sounding speech.

```python
result = client.tts.generate(
    text="Hello world",
    voice="af_heart",    # Voice identifier
    format="mp3",        # "mp3" or "wav"
    speed=1.0,           # 0.5 to 2.0
)
print(result.audio_url)
print(result.duration_seconds)
```

### Speech to Text (Transcription)

Transcribe audio files to text with timestamps.

```python
# From a local file
result = client.transcription.generate(
    audio="recording.mp3",
    model="base",        # "tiny", "base", "small", "medium"
    language="en",       # Optional, auto-detected if omitted
)
print(result.text)

for segment in result.segments:
    print(f"[{segment.start:.1f}s - {segment.end:.1f}s] {segment.text}")

# From a URL
result = client.transcription.generate(
    audio_url="https://example.com/audio.mp3",
)
```

### Text Embeddings

Generate vector embeddings for text.

```python
result = client.embeddings.generate(texts=["Hello", "World"])
print(result.embeddings)   # [[0.1, 0.2, ...], [0.3, 0.4, ...]]
print(result.dimension)    # 1024
print(result.model)        # "bge-m3"
```

### Avatar Video Generation

Generate animated portrait videos. This is an async service — `generate()` submits the task and polls until complete.

```python
# Auto-poll (recommended)
result = client.avatar.generate(
    image="photo.jpg",
    audio="speech.mp3",
    model="sadtalker",       # "sadtalker" or "liveportrait"
    poll_interval=5,         # Seconds between polls
    timeout=600,             # Max wait time
)
print(result.video_url)

# Manual polling
task = client.avatar.create(image="photo.jpg", audio="speech.mp3")
print(task.id, task.status)  # "pending"

# Poll manually
task = client.tasks.get(task.id)
if task.is_completed:
    print(task.output["video_url"])
```

**LivePortrait** (video-driven):

```python
result = client.avatar.generate(
    image="photo.jpg",
    video="driving_video.mp4",
    model="liveportrait",
    flag_stitching=True,
    flag_relative_motion=True,
)
```

## Resources

### Tasks

List and retrieve async tasks.

```python
# List tasks
tasks = client.tasks.list(service="avatar", status="completed")
for task in tasks.data:
    print(task.id, task.status)

# Get a specific task
task = client.tasks.get("task-uuid")
print(task.output)
```

### Credits

Check balance and transaction history.

```python
# Get balance
balance = client.credits.balance()
print(balance.balance)

# Transaction history
history = client.credits.history(type="usage", per_page=50)
for txn in history.data:
    print(txn.amount, txn.description)
```

## Other Methods

```python
# List available services
services = client.services()
for svc in services:
    print(svc.slug, svc.name, svc.min_credits)

# Check rate limits
limits = client.rate_limits()
for limit in limits:
    print(f"{limit.service}: {limit.remaining}/{limit.limit}")
```

## Error Handling

```python
from hudsonly import (
    HudsonlyAI,
    AuthenticationError,
    InsufficientCreditsError,
    RateLimitError,
    ValidationError,
    TaskError,
    TaskTimeoutError,
)

client = HudsonlyAI(api_key="your-token")

try:
    result = client.tts.generate(text="Hello")
except AuthenticationError:
    print("Invalid API key")
except InsufficientCreditsError:
    print("Not enough credits")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")
except ValidationError as e:
    print(f"Validation failed: {e.errors}")
except TaskError as e:
    print(f"Task {e.task_id} failed: {e}")
except TaskTimeoutError:
    print("Task polling timed out")
```

## Context Manager

```python
with HudsonlyAI(api_key="your-token") as client:
    result = client.tts.generate(text="Hello")
```

## Requirements

- Python 3.9+
- [httpx](https://www.python-httpx.org/) (installed automatically)

## License

MIT
