Async Python library for PS4/PS5 second-screen control (wake, standby, button input)
Project description
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.
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 onasyncioandaiohttp
Requirements
- Python 3.11+
- PS4 (firmware ≥ 8.0) or PS5
- Console and computer on the same local network
Installation
pip install playdirector
Quick Start
import asyncio
from playdirector import find, connect, RemoteOperation
from playdirector.credentials import JsonCredentialStorage
async def main():
storage = JsonCredentialStorage()
# Probe a known IP
device = await find("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 (PS4)
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.
The pair() function detects whether the console is a PS4 or PS5 and runs the correct flow automatically.
You'll need:
- Your NPSSO token from the PSN website
- The 8-digit PIN shown on the console:
- PS5 — Settings → System → Remote Play → Link Device
- PS4 — Settings → Remote Play Connection Settings → Add Device
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())
API
Discovery
from playdirector import scan, find
# Scan the network — yields all responding devices
async for device in scan(timeout=5):
print(device.device_type, device.ip, device.status)
# Probe a specific IP — returns one device or None
device = await find("192.168.1.50")
DiscoveredDevice fields:
| Field | Type | Description |
|---|---|---|
ip |
str |
IP address |
name |
str |
Console name |
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
from playdirector import find, wake, connect, standby, send_buttons, go_home
from playdirector.packets import RemoteOperation
device = await find("192.168.1.50")
# Wake from standby (UDP only — no session, call before connecting)
await wake(device, cred)
# Put into standby (PS4 + PS5)
await standby(device, cred)
# Send button inputs (PS4 only)
await send_buttons(device, cred, [RemoteOperation.PS])
await send_buttons(device, cred, [RemoteOperation.UP, RemoteOperation.ENTER])
# Navigate PS5 to home screen (PS5 only)
await go_home(device, cred)
# Open a session manually for multiple operations without reconnecting
async with connect(device, cred) as session:
await session.send_keys([RemoteOperation.UP])
await session.send_keys([RemoteOperation.ENTER])
await session.standby()
Note:
send_buttonsis supported on PS4 only. The PS5 Remote Play protocol does not expose button input — onlywake(),standby(), andgo_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
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
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file playdirector-0.2.2.tar.gz.
File metadata
- Download URL: playdirector-0.2.2.tar.gz
- Upload date:
- Size: 78.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9ef7e91a5cef52a4ca4313556585c0a70a7d3744caa4752540458528a77a74f
|
|
| MD5 |
3cfb238868f6da24f434b36e416b39a7
|
|
| BLAKE2b-256 |
7a14afaf6fd064d011247e2bc6050937268cd948004adbf66122554fe210fe3b
|
Provenance
The following attestation bundles were made for playdirector-0.2.2.tar.gz:
Publisher:
publish.yml on JackJPowell/playdirector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
playdirector-0.2.2.tar.gz -
Subject digest:
e9ef7e91a5cef52a4ca4313556585c0a70a7d3744caa4752540458528a77a74f - Sigstore transparency entry: 1371061600
- Sigstore integration time:
-
Permalink:
JackJPowell/playdirector@f85fca074dde8c004ddbe9a3652efd8c69ad9176 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/JackJPowell
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f85fca074dde8c004ddbe9a3652efd8c69ad9176 -
Trigger Event:
push
-
Statement type:
File details
Details for the file playdirector-0.2.2-py3-none-any.whl.
File metadata
- Download URL: playdirector-0.2.2-py3-none-any.whl
- Upload date:
- Size: 73.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f6223348d62d24563ef1b744cb35ba026c8e9fff2bb8141debaf1df0de37912
|
|
| MD5 |
5957fa1db8d9bda04c2c408da6d77de7
|
|
| BLAKE2b-256 |
0bad2b0e47e3e65eb1e3265479993ed05131bae3dbaa422546a5297f7ecdd484
|
Provenance
The following attestation bundles were made for playdirector-0.2.2-py3-none-any.whl:
Publisher:
publish.yml on JackJPowell/playdirector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
playdirector-0.2.2-py3-none-any.whl -
Subject digest:
6f6223348d62d24563ef1b744cb35ba026c8e9fff2bb8141debaf1df0de37912 - Sigstore transparency entry: 1371061691
- Sigstore integration time:
-
Permalink:
JackJPowell/playdirector@f85fca074dde8c004ddbe9a3652efd8c69ad9176 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/JackJPowell
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f85fca074dde8c004ddbe9a3652efd8c69ad9176 -
Trigger Event:
push
-
Statement type: