Metadata-Version: 2.4
Name: playdirector
Version: 0.1.4
Summary: Async Python library for PS4/PS5 second-screen control (wake, standby, button input)
Project-URL: Homepage, https://github.com/jackjpowell/playdirector
Project-URL: Repository, https://github.com/jackjpowell/playdirector
Project-URL: Issues, https://github.com/jackjpowell/playdirector/issues
Project-URL: Changelog, https://github.com/jackjpowell/playdirector/releases
License: MIT
Keywords: asyncio,playstation,ps4,ps5,remote-control
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Home Automation
Requires-Python: >=3.11
Requires-Dist: aiohttp>=3.9
Requires-Dist: cryptography>=41.0
Description-Content-Type: text/markdown

# playdirector

An async Python library for controlling PS4 and PS5 consoles over your local network — wake from standby, send button inputs, navigate home, and more.

[![PyPI version](https://img.shields.io/pypi/v/playdirector)](https://pypi.org/project/playdirector/)
[![Python](https://img.shields.io/pypi/pyversions/playdirector)](https://pypi.org/project/playdirector/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Features

- **Discover** PS4/PS5 consoles on your local network via UDP broadcast
- **Wake** a console from standby without a full session
- **Connect** and send button inputs (PS4), navigate to home (PS5)
- **Pair** with a PS5 using your PSN credentials (NPSSO token)
- **Credential storage** — JSON files persisted to `~/.config/playdirector/`
- Fully `async`/`await` — built on `asyncio` and `aiohttp`

## Requirements

- Python 3.11+
- PS4 (firmware ≥ 8.0) or PS5
- Console and computer on the same local network

## Installation

```bash
pip install playdirector
```

## Quick Start

```python
import asyncio
from playdirector import discover, connect, RemoteOperation
from playdirector.credentials import JsonCredentialStorage

async def main():
    storage = JsonCredentialStorage()

    # Find the console (pass ip= to target a specific address)
    device = await discover(ip="192.168.1.50")
    if device is None:
        print("Console not found")
        return

    # Load previously paired credentials
    cred = storage.load(device.device_id)
    if cred is None:
        print("No credentials — run pairing first")
        return

    # Connect and send the PS button
    async with connect(device, cred) as session:
        await session.send_keys([RemoteOperation.PS])

asyncio.run(main())
```

## Pairing

Before connecting you need to pair with the console once to obtain credentials.

### PS5

You'll need your [NPSSO token](https://ca.account.sony.com/api/v1/ssocookie) from the PSN website and the 8-digit PIN shown on the console under **Settings → System → Remote Play → Link Device**.

```python
import asyncio
from playdirector import pair
from playdirector.credentials import JsonCredentialStorage

async def main():
    cred = await pair(
        ip="192.168.1.50",
        pin="12345678",       # 8-digit PIN from console
        npsso="your-npsso",   # from https://ca.account.sony.com/api/v1/ssocookie
    )
    JsonCredentialStorage().save(cred)
    print("Paired:", cred.device_id)

asyncio.run(main())
```

### PS4

```python
import asyncio
from playdirector.pairing import pair_ps4_remoteplay
from playdirector.credentials import JsonCredentialStorage

async def main():
    cred = await pair_ps4_remoteplay(
        ip="192.168.1.100",
        pin="12345678",
        npsso="your-npsso",
    )
    JsonCredentialStorage().save(cred)

asyncio.run(main())
```

## API

### Discovery

```python
from playdirector import discover
from playdirector.discovery import DeviceStatus

# Discover all devices on the network
async for device in discover(timeout=5):
    print(device.device_type, device.host, device.status)

# Target a specific IP
device = await discover(ip="192.168.1.50")
```

`DiscoveredDevice` fields:

| Field | Type | Description |
| --- | --- | --- |
| `host` | `str` | IP address |
| `device_type` | `DeviceType` | `PS4` or `PS5` |
| `status` | `DeviceStatus` | `AWAKE` or `STANDBY` |
| `device_id` | `str` | Unique device identifier |
| `running_app_name` | `str \| None` | Currently running app (if awake) |
| `running_app_titleid` | `str \| None` | Title ID of running app |

### Control

```python
from playdirector import connect, wake
from playdirector.packets import RemoteOperation

# Wake from standby (no full session needed)
await wake(device, cred)

# Full session — send button inputs (PS4 only)
async with connect(device, cred) as session:
    await session.send_keys([RemoteOperation.PS])
    await session.send_keys([RemoteOperation.UP, RemoteOperation.ENTER])
    await session.standby()

# Navigate PS5 to home screen (PS5 only)
from playdirector import go_home
await go_home(device, cred)
```

> **Note:** `send_keys` is supported on **PS4 only**. The PS5 Remote Play protocol does not expose button input — only `wake()`, `standby()`, and `go_home()` are available for PS5.

### Available Buttons (`RemoteOperation`) — PS4 only

`UP` · `DOWN` · `LEFT` · `RIGHT` · `ENTER` · `BACK` · `OPTION` · `PS` · `CANCEL`

Multiple buttons can be combined: `RemoteOperation.UP | RemoteOperation.LEFT`

### Credential Storage

```python
from playdirector.credentials import JsonCredentialStorage

storage = JsonCredentialStorage()           # defaults to ~/.config/playdirector/
storage = JsonCredentialStorage("/my/dir") # custom path

storage.save(cred)                         # saves to <dir>/<device_id>.json
cred = storage.load(device_id)             # returns None if not found
storage.delete(device_id)
ids = storage.list_device_ids()
```

## Error Handling

```python
from playdirector import PlayActorError, LoginError, UnsupportedDeviceError

try:
    async with connect(device, cred) as session:
        ...
except LoginError:
    print("Bad credentials")
except UnsupportedDeviceError:
    print("Operation not supported on this console")
except PlayActorError as e:
    print("Protocol error:", e)
```

## License

MIT — see [LICENSE](LICENSE).
