Metadata-Version: 2.4
Name: hadidy-audio
Version: 0.1.2
Summary: Official Python SDK for the Hadidy Audio Transcoding API
Project-URL: Homepage, https://hadidy.com/docs/sdks/python
Project-URL: Repository, https://github.com/hadidybase/hadidy-audio-python
Author-email: Hadidy Audio <sdk@hadidy.com>
License: MIT
Keywords: audio,hadidy,sdk,transcoding
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: httpx<1.0,>=0.28.1
Requires-Dist: pydantic<3.0,>=2.13.4
Provides-Extra: dev
Requires-Dist: mypy>=2.1; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.3; extra == 'dev'
Requires-Dist: pytest>=9.0; extra == 'dev'
Requires-Dist: respx>=0.23; extra == 'dev'
Requires-Dist: ruff>=0.15; extra == 'dev'
Description-Content-Type: text/markdown

# hadidy-audio

Official Python SDK for the [Hadidy Audio](https://hadidy.com) Transcoding API. Supports Python 3.10+ with both synchronous and asynchronous clients. Zero required dependencies beyond `httpx` and `pydantic`.

## Installation

```bash
pip install hadidy-audio
```

> **Python 3.10+ required.** The async client uses `asyncio` — no extra install needed.

---

## Quick Start — Synchronous

```python
from hadidy import AudioClient

client = AudioClient(api_key="had_live_...")

with open("podcast.wav", "rb") as f:
    job = client.jobs.create(f, output_format="mp3", bitrate="192k")

completed = client.jobs.wait_for_completion(job["id"], timeout=300)
print(completed["output_url"])
```

## Quick Start — Async

```python
import asyncio
from hadidy import AsyncAudioClient

async def main():
    async with AsyncAudioClient(api_key="had_live_...") as client:
        async for job in client.jobs.list_all(status="completed"):
            print(f"{job['file_name']} → {job['output_url']}")

asyncio.run(main())
```

---

## Live Streaming

### WHIP (OBS 30+ / Larix — recommended)

```python
from hadidy import AudioClient

client = AudioClient(api_key="had_live_...")

# Create a session — WHIP is the default protocol
session = client.live.create_session(
    title="Friday Night Show",
    ingest_protocol="whip",   # default; can be omitted
    recording_enabled=True,
)
client.live.start_session(session["id"])

print("WHIP endpoint:", session["ingest_url"])
# → https://ingest.hadidy.com/{stream_key}/whip

# OBS 30+: Settings → Stream → Service: WHIP
#   Server: paste session["ingest_url"]  (no separate stream key field)
```

### RTMPS (OBS older / vMix / Streamlabs)

```python
session = client.live.create_session(
    title="Friday Night Show",
    ingest_protocol="rtmp",   # externally TLS-encrypted RTMPS on port 4936
    recording_enabled=True,
)
client.live.start_session(session["id"])

print("RTMPS server:", session["ingest_url"])  # rtmps://rtmps.hadidy.com:4936/
print("Stream key:  ", session["stream_key"])

# OBS: Settings → Stream → Service: Custom RTMPS
#   Server:     paste session["ingest_url"]
#   Stream Key: paste session["stream_key"]
```

### Push track metadata mid-stream

```python
# Call on every track change in your DJ software
client.live.update_now_playing(
    session["id"],
    title="Midnight City",
    artist="M83",
    album="Hurry Up, We're Dreaming",
    cover_url="https://example.com/artwork.jpg",
    # Pre-load the upcoming track on listeners' devices:
    next_title="Resonance",
    next_artist="Home",
    next_cover_url="https://example.com/next-artwork.jpg",
)

# Clear track info between sets
client.live.clear_now_playing(session["id"])
```

### Stop and retrieve recordings

```python
client.live.stop_session(session["id"])

recordings = client.live.list_recordings(session["id"])
for r in recordings:
    print(r["download_url"], r["duration_seconds"])
```

### Async live session

```python
import asyncio
from hadidy import AsyncAudioClient

async def broadcast():
    async with AsyncAudioClient(api_key="had_live_...") as client:
        session = await client.live.create_session(title="Night Show", ingest_protocol="whip")
        await client.live.start_session(session["id"])
        print("Streaming at:", session["ingest_url"])

        # Later, on track change:
        await client.live.update_now_playing(session["id"], title="Atlas", artist="Battles")

        # Stop when done
        await client.live.stop_session(session["id"])

asyncio.run(broadcast())
```

---

## Webhook Verification — FastAPI

```python
from hadidy import WebhookVerifier
from fastapi import FastAPI, Request, HTTPException, Depends
import os

app = FastAPI()
verifier = WebhookVerifier(secret=os.environ["HADIDY_WEBHOOK_SECRET"])

@app.post("/hooks/hadidy")
async def webhook(request: Request, _=Depends(verifier.fastapi_dependency())):
    event = await request.json()
    print(event["type"], event["data"])
    return {"ok": True}
```

---

## API Coverage

| Resource | Methods |
|---|---|
| `client.jobs` | `list()`, `list_all()`, `get()`, `create()`, `delete()`, `get_output()`, `wait_for_completion()` |
| `client.presets` | `list()`, `list_all()`, `get()`, `create()`, `update()`, `delete()` |
| `client.codecs` | `list_codecs()`, `list_formats()`, `capabilities()` |
| `client.analysis` | `analyze(file)` |
| `client.audio` | `waveform_data()`, `silence()`, `get_metadata()`, `update_metadata()`, `concat()` |
| `client.webhooks` | `list()`, `get()`, `create()`, `update()`, `delete()`, `deliveries()`, `test()` |
| `client.billing` | `balance()`, `history()`, `service_costs()` |
| `client.folders` | `list()`, `get()`, `breadcrumb()`, `create()`, `update()`, `delete()`, `move_files()` |
| `client.sharing` | `list()`, `get()`, `create()`, `update()`, `delete()`, `get_public_info()`, `verify_passcode()` |
| `client.live` | `create_session()`, `get_session()`, `list_sessions()`, `list_sessions_all()`, `update_session()`, `start_session()`, `stop_session()`, `restart_session()`, `delete_session()`, `regenerate_key()`, `update_now_playing()`, `clear_now_playing()`, `list_sources()`, `add_source()`, `list_participants()`, `list_recordings()` |

> All methods have async equivalents on `AsyncAudioClient`.

---

## Error Handling

```python
from hadidy import AudioClient, RateLimitError, NotFoundError, ValidationError, HadidyError

client = AudioClient(api_key="had_live_...")

try:
    job = client.jobs.get("nonexistent-id")
except NotFoundError:
    print("Job not found")
except RateLimitError as e:
    print(f"Rate limited — retry after {e.retry_after}s")
except ValidationError as e:
    print(f"Bad request: {e}")
except HadidyError as e:
    print(f"API error {e.status}: {e}")
```

---

## Configuration

```python
client = AudioClient(
    api_key="had_live_...",              # required
    base_url="https://api.hadidy.com",   # default
    timeout=30.0,                         # seconds — default: 30.0
    max_retries=2,                        # default: 2
)
```

---

## License

MIT
