Metadata-Version: 2.4
Name: pydsscodec
Version: 0.1.8
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Programming Language :: Rust
Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Provides-Extra: dev
License-File: LICENSE
Summary: Python bindings for the dss-codec Rust decoder
Keywords: audio,dss,ds2,codec,bindings
Home-Page: https://github.com/gaspardpetit/pydsscodec
Author: Kieran Hirpara, Gaspard Petit
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/gaspardpetit/pydsscodec
Project-URL: Repository, https://github.com/gaspardpetit/pydsscodec
Project-URL: Upstream, https://github.com/gaspardpetit/dss-codec

[![CI](https://github.com/gaspardpetit/pydsscodec/actions/workflows/ci.yaml/badge.svg)](https://github.com/gaspardpetit/pydsscodec/actions/workflows/ci.yaml)
![PyPI - Version](https://img.shields.io/pypi/v/pydsscodec)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pydsscodec)


# pydsscodec

Python bindings for the Rust [`dss-codec`](https://github.com/gaspardpetit/dss-codec) decoder.

`pydsscodec` decodes Olympus DSS and DS2 audio, including Grundig/PH9607 DSS,
directly in Python through a native Rust extension. It mirrors the core
`dss-codec` model: top-level decode and decrypt functions plus streaming
decoders.

## Features

- Decode DSS SP, Grundig DSS SP, DS2 SP, DS2 QP, and DS2 QP7 files from paths
  or bytes
- Normalize encrypted DS2 input back to plain container bytes
- Inspect format, native sample rate, and encryption metadata without decoding
- Stream decode in chunks without forcing a whole-file convenience path
- Load native Rust code in-process instead of shelling out to a CLI
- In our benchmark, achieved roughly 150x faster DSS decoding and 50x faster
  streamed DS2 decoding than the reference Python decoders

## Installation

```bash
pip install pydsscodec
```

If a prebuilt wheel is not available for your platform, installation will build
the Rust extension locally.

Prebuilt wheels are currently published for:

- Linux `x86_64`
- macOS `arm64`
- Windows `x86_64`

Other platforms install from source and require Rust 1.85 or newer.

## Usage

Decode a file:

```python
from pydsscodec import decode_file

audio = decode_file("recording.ds2")
print(audio.format, audio.sample_rate, audio.native_rate, audio.duration_seconds)
print(audio.sample_count)
```

Decode in-memory bytes:

```python
from pydsscodec import decode_bytes

audio = decode_bytes(data)
```

Decrypt encrypted DS2 input to plain container bytes:

```python
from pydsscodec import decrypt_file

plain_ds2 = decrypt_file("encrypted.ds2", password="secret")
```

Stream decode in chunks:

```python
from pydsscodec import DecryptingDecoderStreamer

decoder = DecryptingDecoderStreamer(password="secret")

with open("recording.ds2", "rb") as handle:
    while chunk := handle.read(4096):
        chunk_samples = decoder.push(chunk)
        # process chunk_samples here

final_samples = decoder.finish()

print(decoder.format(), decoder.native_rate(), len(final_samples))
```

Detect the container format without decoding:

```python
from pydsscodec import detect_format

fmt = detect_format(data)
print(fmt)  # e.g. "dss_sp", "grundig_sp", "ds2_sp", "ds2_qp", or "ds2_qp7"
```

Inspect format and encryption metadata without decoding:

```python
from pydsscodec import inspect_file

info = inspect_file("recording.ds2")
print(info.format, info.native_rate, info.encryption, info.encryption_mode)
```

## API

Top-level functions:

- `decode_file(path, password=None) -> DecodedAudio`
- `decode_bytes(data, password=None) -> DecodedAudio`
- `decrypt_file(path, password=None) -> bytes`
- `decrypt_bytes(data, password=None) -> bytes`
- `detect_format(data) -> str | None`
- `inspect_file(path) -> FileInfo`
- `inspect_bytes(data) -> FileInfo`

Streamer classes:

- `StreamingDecoder()`
- `DecryptStreamer(password=None)`
- `DecryptingDecoderStreamer(password=None)`

Streamer methods mirror the Rust API:

- `push(data)`
- `finish()`
- `format()`
- `native_rate()`

Bulk decode returns a `DecodedAudio` object with:

- `samples`: decoded mono samples as `list[float]`
- `sample_rate`: sample rate of the returned samples
- `native_rate`: original sample rate of the source codec
- `format`: one of `"dss_sp"`, `"grundig_sp"`, `"ds2_sp"`, `"ds2_qp"`, or
  `"ds2_qp7"`
- `sample_count`
- `duration_seconds`

Inspection returns a `FileInfo` object with:

- `format`: detected audio format
- `native_rate`: native sample rate in Hz
- `encryption`: `"none"`, `"ds2_aes128"`, `"ds2_aes256"`, or `"unknown"`
- `encryption_mode`: the numeric DS2 encryption mode, or `None` for plain input

## Notes

- The streaming classes are the preferred scalable interface for large inputs.
- Bulk decode returns samples as a Python list.

