playdirector 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,89 @@
1
+ # =============================================================================
2
+ # Python
3
+ # =============================================================================
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.pyo
8
+ *.pyd
9
+
10
+ # Distribution / packaging
11
+ dist/
12
+ build/
13
+ *.egg-info/
14
+ *.egg
15
+ MANIFEST
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ env/
21
+ .env/
22
+
23
+ # uv
24
+ .uv/
25
+ # uv.lock is intentionally NOT ignored — commit it for reproducible installs
26
+
27
+ # hatch-vcs generated version file
28
+ playdirector/_version.py
29
+
30
+ # Installer caches
31
+ pip-wheel-metadata/
32
+
33
+ # =============================================================================
34
+ # Testing & coverage
35
+ # =============================================================================
36
+ .pytest_cache/
37
+ .coverage
38
+ .coverage.*
39
+ coverage.xml
40
+ htmlcov/
41
+
42
+ # =============================================================================
43
+ # Type checking
44
+ # =============================================================================
45
+ .mypy_cache/
46
+ .dmypy.json
47
+ dmypy.json
48
+ .pyright/
49
+
50
+ # =============================================================================
51
+ # Linting
52
+ # =============================================================================
53
+ .ruff_cache/
54
+
55
+ # =============================================================================
56
+ # macOS
57
+ # =============================================================================
58
+ .DS_Store
59
+ .AppleDouble
60
+ .LSOverride
61
+ Icon?
62
+ ._*
63
+ .Spotlight-V100
64
+ .Trashes
65
+
66
+ # =============================================================================
67
+ # Editors / IDEs
68
+ # =============================================================================
69
+ .vscode/
70
+ *.code-workspace
71
+ .idea/
72
+ *.iml
73
+ *.swp
74
+ *.swo
75
+ *~
76
+
77
+ # =============================================================================
78
+ # Credentials (never commit these)
79
+ # =============================================================================
80
+ *_credentials.json
81
+ *.key
82
+ *.pem
83
+ secrets.*
84
+
85
+ # =============================================================================
86
+ # Misc
87
+ # =============================================================================
88
+ *.pcap
89
+ *.log
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: playdirector
3
+ Version: 0.1.0
4
+ Summary: Async Python library for PS4/PS5 second-screen control (wake, standby, button input)
5
+ Project-URL: Homepage, https://github.com/jackjpowell/playdirector
6
+ Project-URL: Repository, https://github.com/jackjpowell/playdirector
7
+ Project-URL: Issues, https://github.com/jackjpowell/playdirector/issues
8
+ Project-URL: Changelog, https://github.com/jackjpowell/playdirector/releases
9
+ License: MIT
10
+ Keywords: asyncio,playstation,ps4,ps5,remote-control
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: AsyncIO
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Home Automation
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: aiohttp>=3.9
21
+ Requires-Dist: cryptography>=41.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # playdirector
25
+
26
+ An async Python library for controlling PS4 and PS5 consoles over your local network — wake from standby, send button inputs, navigate home, and more.
27
+
28
+ [![PyPI version](https://img.shields.io/pypi/v/playdirector)](https://pypi.org/project/playdirector/)
29
+ [![Python](https://img.shields.io/pypi/pyversions/playdirector)](https://pypi.org/project/playdirector/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
31
+
32
+ ## Features
33
+
34
+ - **Discover** PS4/PS5 consoles on your local network via UDP broadcast
35
+ - **Wake** a console from standby without a full session
36
+ - **Connect** and send button inputs (PS4), navigate to home (PS5)
37
+ - **Pair** with a PS5 using your PSN credentials (NPSSO token)
38
+ - **Credential storage** — JSON files persisted to `~/.config/playdirector/`
39
+ - Fully `async`/`await` — built on `asyncio` and `aiohttp`
40
+
41
+ ## Requirements
42
+
43
+ - Python 3.11+
44
+ - PS4 (firmware ≥ 8.0) or PS5
45
+ - Console and computer on the same local network
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install playdirector
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ import asyncio
57
+ from playdirector import discover, connect, RemoteOperation
58
+ from playdirector.credentials import JsonCredentialStorage
59
+
60
+ async def main():
61
+ storage = JsonCredentialStorage()
62
+
63
+ # Find the console (pass ip= to target a specific address)
64
+ device = await discover(ip="192.168.1.50")
65
+ if device is None:
66
+ print("Console not found")
67
+ return
68
+
69
+ # Load previously paired credentials
70
+ cred = storage.load(device.device_id)
71
+ if cred is None:
72
+ print("No credentials — run pairing first")
73
+ return
74
+
75
+ # Connect and send the PS button
76
+ async with connect(device, cred) as session:
77
+ await session.send_keys([RemoteOperation.PS])
78
+
79
+ asyncio.run(main())
80
+ ```
81
+
82
+ ## Pairing
83
+
84
+ Before connecting you need to pair with the console once to obtain credentials.
85
+
86
+ ### PS5
87
+
88
+ 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**.
89
+
90
+ ```python
91
+ import asyncio
92
+ from playdirector import pair
93
+ from playdirector.credentials import JsonCredentialStorage
94
+
95
+ async def main():
96
+ cred = await pair(
97
+ ip="192.168.1.50",
98
+ pin="12345678", # 8-digit PIN from console
99
+ npsso="your-npsso", # from https://ca.account.sony.com/api/v1/ssocookie
100
+ )
101
+ JsonCredentialStorage().save(cred)
102
+ print("Paired:", cred.device_id)
103
+
104
+ asyncio.run(main())
105
+ ```
106
+
107
+ ### PS4
108
+
109
+ ```python
110
+ import asyncio
111
+ from playdirector.pairing import pair_ps4_remoteplay
112
+ from playdirector.credentials import JsonCredentialStorage
113
+
114
+ async def main():
115
+ cred = await pair_ps4_remoteplay(
116
+ ip="192.168.1.100",
117
+ pin="12345678",
118
+ npsso="your-npsso",
119
+ )
120
+ JsonCredentialStorage().save(cred)
121
+
122
+ asyncio.run(main())
123
+ ```
124
+
125
+ ## API
126
+
127
+ ### Discovery
128
+
129
+ ```python
130
+ from playdirector import discover
131
+ from playdirector.discovery import DeviceStatus
132
+
133
+ # Discover all devices on the network
134
+ async for device in discover(timeout=5):
135
+ print(device.device_type, device.host, device.status)
136
+
137
+ # Target a specific IP
138
+ device = await discover(ip="192.168.1.50")
139
+ ```
140
+
141
+ `DiscoveredDevice` fields:
142
+
143
+ | Field | Type | Description |
144
+ | --- | --- | --- |
145
+ | `host` | `str` | IP address |
146
+ | `device_type` | `DeviceType` | `PS4` or `PS5` |
147
+ | `status` | `DeviceStatus` | `AWAKE` or `STANDBY` |
148
+ | `device_id` | `str` | Unique device identifier |
149
+ | `running_app_name` | `str \| None` | Currently running app (if awake) |
150
+ | `running_app_titleid` | `str \| None` | Title ID of running app |
151
+
152
+ ### Control
153
+
154
+ ```python
155
+ from playdirector import connect, wake
156
+ from playdirector.packets import RemoteOperation
157
+
158
+ # Wake from standby (no full session needed)
159
+ await wake(device, cred)
160
+
161
+ # Full session — send button inputs (PS4)
162
+ async with connect(device, cred) as session:
163
+ await session.send_keys([RemoteOperation.PS])
164
+ await session.send_keys([RemoteOperation.UP, RemoteOperation.ENTER])
165
+ await session.standby()
166
+
167
+ # Navigate PS5 to home screen
168
+ from playdirector import go_home
169
+ await go_home(device, cred)
170
+ ```
171
+
172
+ ### Available Buttons (`RemoteOperation`)
173
+
174
+ `UP` · `DOWN` · `LEFT` · `RIGHT` · `ENTER` · `BACK` · `OPTION` · `PS` · `CANCEL`
175
+
176
+ Multiple buttons can be combined: `RemoteOperation.UP | RemoteOperation.LEFT`
177
+
178
+ ### Credential Storage
179
+
180
+ ```python
181
+ from playdirector.credentials import JsonCredentialStorage
182
+
183
+ storage = JsonCredentialStorage() # defaults to ~/.config/playdirector/
184
+ storage = JsonCredentialStorage("/my/dir") # custom path
185
+
186
+ storage.save(cred) # saves to <dir>/<device_id>.json
187
+ cred = storage.load(device_id) # returns None if not found
188
+ storage.delete(device_id)
189
+ ids = storage.list_device_ids()
190
+ ```
191
+
192
+ ## Error Handling
193
+
194
+ ```python
195
+ from playdirector import PlayActorError, LoginError, UnsupportedDeviceError
196
+
197
+ try:
198
+ async with connect(device, cred) as session:
199
+ ...
200
+ except LoginError:
201
+ print("Bad credentials")
202
+ except UnsupportedDeviceError:
203
+ print("Operation not supported on this console")
204
+ except PlayActorError as e:
205
+ print("Protocol error:", e)
206
+ ```
207
+
208
+ ## License
209
+
210
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,187 @@
1
+ # playdirector
2
+
3
+ An async Python library for controlling PS4 and PS5 consoles over your local network — wake from standby, send button inputs, navigate home, and more.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/playdirector)](https://pypi.org/project/playdirector/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/playdirector)](https://pypi.org/project/playdirector/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+
9
+ ## Features
10
+
11
+ - **Discover** PS4/PS5 consoles on your local network via UDP broadcast
12
+ - **Wake** a console from standby without a full session
13
+ - **Connect** and send button inputs (PS4), navigate to home (PS5)
14
+ - **Pair** with a PS5 using your PSN credentials (NPSSO token)
15
+ - **Credential storage** — JSON files persisted to `~/.config/playdirector/`
16
+ - Fully `async`/`await` — built on `asyncio` and `aiohttp`
17
+
18
+ ## Requirements
19
+
20
+ - Python 3.11+
21
+ - PS4 (firmware ≥ 8.0) or PS5
22
+ - Console and computer on the same local network
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install playdirector
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```python
33
+ import asyncio
34
+ from playdirector import discover, connect, RemoteOperation
35
+ from playdirector.credentials import JsonCredentialStorage
36
+
37
+ async def main():
38
+ storage = JsonCredentialStorage()
39
+
40
+ # Find the console (pass ip= to target a specific address)
41
+ device = await discover(ip="192.168.1.50")
42
+ if device is None:
43
+ print("Console not found")
44
+ return
45
+
46
+ # Load previously paired credentials
47
+ cred = storage.load(device.device_id)
48
+ if cred is None:
49
+ print("No credentials — run pairing first")
50
+ return
51
+
52
+ # Connect and send the PS button
53
+ async with connect(device, cred) as session:
54
+ await session.send_keys([RemoteOperation.PS])
55
+
56
+ asyncio.run(main())
57
+ ```
58
+
59
+ ## Pairing
60
+
61
+ Before connecting you need to pair with the console once to obtain credentials.
62
+
63
+ ### PS5
64
+
65
+ 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**.
66
+
67
+ ```python
68
+ import asyncio
69
+ from playdirector import pair
70
+ from playdirector.credentials import JsonCredentialStorage
71
+
72
+ async def main():
73
+ cred = await pair(
74
+ ip="192.168.1.50",
75
+ pin="12345678", # 8-digit PIN from console
76
+ npsso="your-npsso", # from https://ca.account.sony.com/api/v1/ssocookie
77
+ )
78
+ JsonCredentialStorage().save(cred)
79
+ print("Paired:", cred.device_id)
80
+
81
+ asyncio.run(main())
82
+ ```
83
+
84
+ ### PS4
85
+
86
+ ```python
87
+ import asyncio
88
+ from playdirector.pairing import pair_ps4_remoteplay
89
+ from playdirector.credentials import JsonCredentialStorage
90
+
91
+ async def main():
92
+ cred = await pair_ps4_remoteplay(
93
+ ip="192.168.1.100",
94
+ pin="12345678",
95
+ npsso="your-npsso",
96
+ )
97
+ JsonCredentialStorage().save(cred)
98
+
99
+ asyncio.run(main())
100
+ ```
101
+
102
+ ## API
103
+
104
+ ### Discovery
105
+
106
+ ```python
107
+ from playdirector import discover
108
+ from playdirector.discovery import DeviceStatus
109
+
110
+ # Discover all devices on the network
111
+ async for device in discover(timeout=5):
112
+ print(device.device_type, device.host, device.status)
113
+
114
+ # Target a specific IP
115
+ device = await discover(ip="192.168.1.50")
116
+ ```
117
+
118
+ `DiscoveredDevice` fields:
119
+
120
+ | Field | Type | Description |
121
+ | --- | --- | --- |
122
+ | `host` | `str` | IP address |
123
+ | `device_type` | `DeviceType` | `PS4` or `PS5` |
124
+ | `status` | `DeviceStatus` | `AWAKE` or `STANDBY` |
125
+ | `device_id` | `str` | Unique device identifier |
126
+ | `running_app_name` | `str \| None` | Currently running app (if awake) |
127
+ | `running_app_titleid` | `str \| None` | Title ID of running app |
128
+
129
+ ### Control
130
+
131
+ ```python
132
+ from playdirector import connect, wake
133
+ from playdirector.packets import RemoteOperation
134
+
135
+ # Wake from standby (no full session needed)
136
+ await wake(device, cred)
137
+
138
+ # Full session — send button inputs (PS4)
139
+ async with connect(device, cred) as session:
140
+ await session.send_keys([RemoteOperation.PS])
141
+ await session.send_keys([RemoteOperation.UP, RemoteOperation.ENTER])
142
+ await session.standby()
143
+
144
+ # Navigate PS5 to home screen
145
+ from playdirector import go_home
146
+ await go_home(device, cred)
147
+ ```
148
+
149
+ ### Available Buttons (`RemoteOperation`)
150
+
151
+ `UP` · `DOWN` · `LEFT` · `RIGHT` · `ENTER` · `BACK` · `OPTION` · `PS` · `CANCEL`
152
+
153
+ Multiple buttons can be combined: `RemoteOperation.UP | RemoteOperation.LEFT`
154
+
155
+ ### Credential Storage
156
+
157
+ ```python
158
+ from playdirector.credentials import JsonCredentialStorage
159
+
160
+ storage = JsonCredentialStorage() # defaults to ~/.config/playdirector/
161
+ storage = JsonCredentialStorage("/my/dir") # custom path
162
+
163
+ storage.save(cred) # saves to <dir>/<device_id>.json
164
+ cred = storage.load(device_id) # returns None if not found
165
+ storage.delete(device_id)
166
+ ids = storage.list_device_ids()
167
+ ```
168
+
169
+ ## Error Handling
170
+
171
+ ```python
172
+ from playdirector import PlayActorError, LoginError, UnsupportedDeviceError
173
+
174
+ try:
175
+ async with connect(device, cred) as session:
176
+ ...
177
+ except LoginError:
178
+ print("Bad credentials")
179
+ except UnsupportedDeviceError:
180
+ print("Operation not supported on this console")
181
+ except PlayActorError as e:
182
+ print("Protocol error:", e)
183
+ ```
184
+
185
+ ## License
186
+
187
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,58 @@
1
+ """
2
+ playdirector — async Python library for PS4/PS5 second-screen control.
3
+
4
+ Quick start::
5
+
6
+ import asyncio
7
+ from playdirector import discover, connect, RemoteOperation
8
+
9
+ async def main():
10
+ device = await discover("192.168.1.50")
11
+ from playdirector.credentials import JsonCredentialStorage
12
+ cred = JsonCredentialStorage().load(device.device_id)
13
+ async with connect(device, cred) as session:
14
+ await session.send_keys([RemoteOperation.PS])
15
+
16
+ asyncio.run(main())
17
+ """
18
+
19
+ from .playstation import (
20
+ DeviceStatus,
21
+ DeviceType,
22
+ DiscoveredDevice,
23
+ InternalRemoteOperation,
24
+ JsonCredentialStorage,
25
+ LoginError,
26
+ PlayActorError,
27
+ RemoteOperation,
28
+ RemotePlayCredentials,
29
+ SecondScreenCredentials,
30
+ Session,
31
+ UnsupportedDeviceError,
32
+ connect,
33
+ discover,
34
+ go_home,
35
+ pair,
36
+ wake,
37
+ )
38
+
39
+ __version__ = "0.1.0"
40
+ __all__ = [
41
+ "connect",
42
+ "discover",
43
+ "go_home",
44
+ "pair",
45
+ "wake",
46
+ "DiscoveredDevice",
47
+ "DeviceType",
48
+ "DeviceStatus",
49
+ "Session",
50
+ "RemoteOperation",
51
+ "InternalRemoteOperation",
52
+ "SecondScreenCredentials",
53
+ "RemotePlayCredentials",
54
+ "JsonCredentialStorage",
55
+ "PlayActorError",
56
+ "LoginError",
57
+ "UnsupportedDeviceError",
58
+ ]
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None