macos-mediaremote-python 0.1.0a1__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.
- macos_mediaremote_python-0.1.0a1/LICENSE +28 -0
- macos_mediaremote_python-0.1.0a1/MANIFEST.in +8 -0
- macos_mediaremote_python-0.1.0a1/PKG-INFO +345 -0
- macos_mediaremote_python-0.1.0a1/README.md +327 -0
- macos_mediaremote_python-0.1.0a1/examples/read_once.py +18 -0
- macos_mediaremote_python-0.1.0a1/examples/verify_installed.py +99 -0
- macos_mediaremote_python-0.1.0a1/examples/watch.py +17 -0
- macos_mediaremote_python-0.1.0a1/licenses/mediaremote-adapter.txt +28 -0
- macos_mediaremote_python-0.1.0a1/native_build.py +66 -0
- macos_mediaremote_python-0.1.0a1/pyproject.toml +34 -0
- macos_mediaremote_python-0.1.0a1/release_checks.py +51 -0
- macos_mediaremote_python-0.1.0a1/setup.cfg +4 -0
- macos_mediaremote_python-0.1.0a1/setup.py +35 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote/__init__.py +12 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote/_process.py +120 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote/client.py +175 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote/errors.py +30 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote/models.py +78 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote/py.typed +0 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote_python.egg-info/PKG-INFO +345 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote_python.egg-info/SOURCES.txt +26 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote_python.egg-info/dependency_links.txt +1 -0
- macos_mediaremote_python-0.1.0a1/src/macos_mediaremote_python.egg-info/top_level.txt +1 -0
- macos_mediaremote_python-0.1.0a1/tests/conftest.py +34 -0
- macos_mediaremote_python-0.1.0a1/tests/test_client.py +283 -0
- macos_mediaremote_python-0.1.0a1/tests/test_models.py +51 -0
- macos_mediaremote_python-0.1.0a1/tests/test_release_checks.py +56 -0
- macos_mediaremote_python-0.1.0a1/upstream.lock.json +11 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, macos-mediaremote-python contributors
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: macos-mediaremote-python
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Unofficial asyncio wrapper for ungive/mediaremote-adapter
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
Project-URL: Homepage, https://github.com/Mai-icy/macos-mediaremote-python
|
|
7
|
+
Project-URL: Repository, https://github.com/Mai-icy/macos-mediaremote-python
|
|
8
|
+
Project-URL: Issues, https://github.com/Mai-icy/macos-mediaremote-python/issues
|
|
9
|
+
Project-URL: Documentation, https://github.com/Mai-icy/macos-mediaremote-python#readme
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
License-File: licenses/mediaremote-adapter.txt
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# macos-mediaremote-python
|
|
20
|
+
|
|
21
|
+
An **unofficial** Python wrapper for
|
|
22
|
+
[ungive/mediaremote-adapter](https://github.com/ungive/mediaremote-adapter).
|
|
23
|
+
It provides asyncio APIs to read macOS Now Playing metadata, subscribe to
|
|
24
|
+
updates, and control the current player.
|
|
25
|
+
|
|
26
|
+
Import it as `macos_mediaremote`. The distribution name is
|
|
27
|
+
`macos-mediaremote-python`, and the current alpha version is `0.1.0a1`.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Requires macOS and Python 3.11+. Install the alpha release in a virtual environment:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
python3 -m venv /tmp/mediaremote-demo
|
|
35
|
+
/tmp/mediaremote-demo/bin/python -m pip install macos-mediaremote-python==0.1.0a1
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Compatible macOS installations use the bundled universal2 wheel. For local
|
|
39
|
+
development, see [Building from source](#building-from-source).
|
|
40
|
+
|
|
41
|
+
The wheel includes the framework, upstream Perl script, and licenses. Installing,
|
|
42
|
+
importing, and running an installed wheel never downloads native code. Wheel users
|
|
43
|
+
do not need Git, CMake, Xcode, or Homebrew.
|
|
44
|
+
|
|
45
|
+
At runtime, the wrapper invokes macOS's `/usr/bin/perl`, preserving the upstream
|
|
46
|
+
`DynaLoader` mechanism for loading the framework. Python does not load the private
|
|
47
|
+
framework directly.
|
|
48
|
+
|
|
49
|
+
## Read the current state
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
from macos_mediaremote import MediaRemote
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
remote = MediaRemote()
|
|
57
|
+
state = await remote.get()
|
|
58
|
+
if state is not None:
|
|
59
|
+
print(state.title, state.artist, state.playing)
|
|
60
|
+
print(state.bundle_identifier)
|
|
61
|
+
print(state.elapsed_time, state.duration) # Seconds
|
|
62
|
+
print(state.raw.get("parentApplicationBundleIdentifier"))
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Inside an existing asyncio application, use `await remote.get()` directly.
|
|
68
|
+
Each `get()` call starts a short-lived helper process. Importing the package and
|
|
69
|
+
constructing `MediaRemote()` have no process or network side effects.
|
|
70
|
+
|
|
71
|
+
## Subscribe to updates
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import asyncio
|
|
75
|
+
from macos_mediaremote import MediaRemote
|
|
76
|
+
|
|
77
|
+
async def main():
|
|
78
|
+
async with MediaRemote().stream() as events:
|
|
79
|
+
async for state in events:
|
|
80
|
+
if state is None:
|
|
81
|
+
print("No current media session")
|
|
82
|
+
else:
|
|
83
|
+
print(state.title, state.playing)
|
|
84
|
+
|
|
85
|
+
asyncio.run(main())
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Each `stream()` owns one persistent helper process and must be used with
|
|
89
|
+
`async with`. Entering the context waits up to `initialization_timeout` seconds
|
|
90
|
+
for the first valid snapshot, which is retained for iteration. Subsequent silence
|
|
91
|
+
has no idle timeout. Every event is a complete snapshot (`--no-diff` upstream).
|
|
92
|
+
|
|
93
|
+
Upstream may emit an empty snapshot before the current media state arrives.
|
|
94
|
+
The first `None` does not mean initialization has settled.
|
|
95
|
+
|
|
96
|
+
- Leaving the context after `break`, a consumer exception, or cancellation closes
|
|
97
|
+
the helper. Cleanup sends SIGTERM, escalates to SIGKILL after one second if
|
|
98
|
+
necessary, and reaps the process.
|
|
99
|
+
- Cancellation preserves `asyncio.CancelledError`. `await events.aclose()` is
|
|
100
|
+
idempotent. Do not read the same iterator concurrently or call `aclose()` while
|
|
101
|
+
another task is still awaiting `anext()`. Cancel and await that task first.
|
|
102
|
+
- Clean EOF before the first snapshot is a protocol error. Clean EOF after a
|
|
103
|
+
snapshot ends iteration. A nonzero exit raises `HelperError`; it is not retried.
|
|
104
|
+
- stdout uses upstream's newline-delimited JSON protocol. UTF-8 characters may
|
|
105
|
+
span reads, and one read may contain multiple events. The stream does not use
|
|
106
|
+
`--human-readable` or accept a single pretty-printed JSON event spanning lines.
|
|
107
|
+
- The default limit is 4 MiB per event or complete command response. Exceeding it
|
|
108
|
+
raises an error and closes the helper. Slow consumers apply backpressure through
|
|
109
|
+
bounded pipes instead of accumulating an unbounded Python event queue.
|
|
110
|
+
- stderr is continuously drained, retaining only its last 64 KiB. Nonfatal stderr
|
|
111
|
+
does not stop successful operations. Inspect `events.stderr` during a stream,
|
|
112
|
+
or `HelperError.stderr` / `HelperTimeoutError.stderr` on failure. Successful
|
|
113
|
+
short-lived commands do not expose stderr.
|
|
114
|
+
|
|
115
|
+
## Control the current player
|
|
116
|
+
|
|
117
|
+
These methods change real playback state. Call them only when intended:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
await remote.play()
|
|
121
|
+
await remote.pause()
|
|
122
|
+
await remote.toggle_play_pause()
|
|
123
|
+
await remote.previous_track()
|
|
124
|
+
await remote.next_track()
|
|
125
|
+
await remote.seek(42.5) # Seconds; sends 42500000 microseconds upstream
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
You can also import `Command` from the package and call
|
|
129
|
+
`await remote.send(Command.PLAY)`.
|
|
130
|
+
|
|
131
|
+
Arguments are validated and passed as a subprocess argument list, never through
|
|
132
|
+
a shell. `seek()` accepts finite, nonnegative seconds, rounded to the nearest
|
|
133
|
+
integer microsecond using Python's `round()`, within the upstream signed 64-bit
|
|
134
|
+
range. Seeking to zero is supported.
|
|
135
|
+
|
|
136
|
+
**Commands target the player selected by the system at dispatch time.** The API
|
|
137
|
+
does not enumerate or lock arbitrary sessions. If the current player changes
|
|
138
|
+
from A to B after `get()`, the next control may go to B. This version does not
|
|
139
|
+
provide target-player checks; even a future check would leave a race between
|
|
140
|
+
checking and dispatching.
|
|
141
|
+
|
|
142
|
+
Success means the upstream command exited successfully, not that the player
|
|
143
|
+
acknowledged or applied it. In particular, seeking has no player acknowledgement.
|
|
144
|
+
Upstream's implicit application-launch behavior is preserved, so some commands
|
|
145
|
+
may launch a player.
|
|
146
|
+
|
|
147
|
+
## Types, units, and errors
|
|
148
|
+
|
|
149
|
+
`get()` and stream events return `NowPlaying | None`. A null or empty snapshot
|
|
150
|
+
means no session is reported. Nonempty snapshots preserve missing and unknown
|
|
151
|
+
fields. `--allow-missing-title` is enabled by default, so a session can have no
|
|
152
|
+
title. Missing fields are not replaced with empty strings or zero.
|
|
153
|
+
|
|
154
|
+
| Attribute | Type and unit |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| `bundle_identifier`, `title`, `artist`, `album` | `str` or `None` |
|
|
157
|
+
| `playing` | `bool` or `None` |
|
|
158
|
+
| `duration`, `elapsed_time` | `float` or `None`, in seconds; elapsed time is the position at `timestamp`, not a live clock |
|
|
159
|
+
| `timestamp` | `float` or `None`, Unix epoch seconds |
|
|
160
|
+
| `playback_rate` | `float` or `None`, playback multiplier |
|
|
161
|
+
| `raw` | Top-level read-only mapping preserving upstream keys, nulls, and original units; unknown nested objects are not recursively frozen |
|
|
162
|
+
|
|
163
|
+
Public time attributes, `seek()`, and all timeouts use seconds. `raw` deliberately
|
|
164
|
+
preserves upstream microsecond fields such as `durationMicros`, `elapsedTimeMicros`,
|
|
165
|
+
and `timestampEpochMicros`. This version does not extrapolate playback progress.
|
|
166
|
+
|
|
167
|
+
Artwork is omitted by default. With `MediaRemote(include_artwork=True)`, `raw` may
|
|
168
|
+
include base64-encoded `artworkData`, depending on the player.
|
|
169
|
+
|
|
170
|
+
The complete configuration is:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
remote = MediaRemote(
|
|
174
|
+
timeout=5.0,
|
|
175
|
+
initialization_timeout=5.0,
|
|
176
|
+
include_artwork=False,
|
|
177
|
+
max_output_bytes=4 * 1024 * 1024,
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The output limit can be set between 1 KiB and 64 MiB.
|
|
182
|
+
|
|
183
|
+
All public exceptions are available from the package root:
|
|
184
|
+
|
|
185
|
+
- `MediaRemoteError`: common runtime error base class.
|
|
186
|
+
- `HelperError`: helper startup or exit failure, with `returncode` and `stderr`.
|
|
187
|
+
- `HelperTimeoutError`: command or initialization timeout; also a `TimeoutError`.
|
|
188
|
+
- `ProtocolError`: invalid or oversized protocol output.
|
|
189
|
+
- `UnsupportedPlatformError`: attempted use on a non-macOS platform.
|
|
190
|
+
|
|
191
|
+
Invalid API arguments raise `ValueError`.
|
|
192
|
+
|
|
193
|
+
## Upstream and packaging
|
|
194
|
+
|
|
195
|
+
Package version `0.1.0a1` pins upstream
|
|
196
|
+
[v0.7.7](https://github.com/ungive/mediaremote-adapter/releases/tag/v0.7.7), commit
|
|
197
|
+
[`e3ff5021eb0875858bd05f48d2e9ba2e962d1cf6`](https://github.com/ungive/mediaremote-adapter/tree/e3ff5021eb0875858bd05f48d2e9ba2e962d1cf6).
|
|
198
|
+
[upstream.lock.json](https://github.com/Mai-icy/macos-mediaremote-python/blob/main/upstream.lock.json) records the archive SHA-256, deployment
|
|
199
|
+
target, and package-to-upstream version mapping. Each build extracts verified
|
|
200
|
+
archive bytes into a fresh directory. It does not build from an edited checkout
|
|
201
|
+
or fetch a moving `latest` version at runtime.
|
|
202
|
+
|
|
203
|
+
The build uses standard `setuptools.build_meta` with small hooks to build the
|
|
204
|
+
native resources and set the wheel tag. See the
|
|
205
|
+
[setuptools customization documentation](https://setuptools.pypa.io/en/latest/userguide/extension.html)
|
|
206
|
+
and [platform tag specification](https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/).
|
|
207
|
+
|
|
208
|
+
Upstream CMake builds both arm64 and x86_64. This package produces a universal2
|
|
209
|
+
wheel tagged `py3-none-macosx_11_0_universal2`, with `Root-Is-Purelib: false`.
|
|
210
|
+
`none` means the helper is independent of the CPython ABI; the platform tag must
|
|
211
|
+
not be changed to `any`.
|
|
212
|
+
|
|
213
|
+
The upstream native and Perl sources are unmodified. Packaging flattens the
|
|
214
|
+
framework's `Versions/A` directory to avoid relying on wheel installers to
|
|
215
|
+
preserve symlinks, changes its dylib install name to
|
|
216
|
+
`@rpath/MediaRemoteAdapter.framework/MediaRemoteAdapter`, and applies and verifies
|
|
217
|
+
an ad-hoc signature. The upstream test client is not bundled, and the `test`
|
|
218
|
+
command, which may create a synthetic Now Playing entry, is not exposed or run.
|
|
219
|
+
|
|
220
|
+
## Building from source
|
|
221
|
+
|
|
222
|
+
Requires macOS, Xcode Command Line Tools, CMake, and Python 3.11.8+:
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
python3 -m venv .venv
|
|
226
|
+
.venv/bin/python -m pip install 'setuptools>=77,<85' build pytest pytest-asyncio
|
|
227
|
+
.venv/bin/python -m pytest -q
|
|
228
|
+
.venv/bin/python -m build
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Install the resulting wheel into a separate environment to verify it:
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
python3 -m venv /tmp/mediaremote-local
|
|
235
|
+
/tmp/mediaremote-local/bin/python -m pip install --no-index --no-deps dist/*.whl
|
|
236
|
+
/tmp/mediaremote-local/bin/python examples/read_once.py
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`python -m build` creates an sdist, then builds a wheel from it. The sdist includes
|
|
240
|
+
this project's build scripts, lock file, licenses, tests, and examples, but not
|
|
241
|
+
the upstream source archive. Source installation requires native build tools and
|
|
242
|
+
network access to the pinned GitHub archive. Build isolation also installs the
|
|
243
|
+
build dependencies declared in `pyproject.toml`.
|
|
244
|
+
|
|
245
|
+
In an existing source directory, `.build/upstream/source.tar.gz` can be reused as
|
|
246
|
+
a SHA-256-verified cache. Building from an sdist downloads it again. These steps
|
|
247
|
+
only happen at build time.
|
|
248
|
+
|
|
249
|
+
## Support and limitations
|
|
250
|
+
|
|
251
|
+
- The native deployment target is macOS 11.0. This is a binary build target,
|
|
252
|
+
not a runtime guarantee for every macOS 11+ release. Python's own deployment
|
|
253
|
+
requirements also apply.
|
|
254
|
+
- Live media metadata and streaming have been verified on the development Mac,
|
|
255
|
+
running macOS 26.5.1 on Apple Silicon. Hosted CI separately checks installation
|
|
256
|
+
and read-only helper execution on Intel and Apple Silicon; it does not exercise
|
|
257
|
+
real desktop players.
|
|
258
|
+
- This package depends on private MediaRemote APIs and the system Perl access
|
|
259
|
+
mechanism. Future macOS updates may break it.
|
|
260
|
+
- Browsers and other players may report different fields, omit titles or artwork,
|
|
261
|
+
or ignore some commands.
|
|
262
|
+
- The pinned upstream `stream.m` still calls `requestAll()` before registering
|
|
263
|
+
notifications. This leaves a potential initialization window for missed updates.
|
|
264
|
+
The first event is not an atomic subscription-ready barrier. This package
|
|
265
|
+
preserves upstream initialization behavior.
|
|
266
|
+
- `get()` returning `None` alone cannot prove private API access is working: no
|
|
267
|
+
active session and an access failure may be hard to distinguish. Read-only
|
|
268
|
+
verification does not create synthetic media to resolve that uncertainty.
|
|
269
|
+
|
|
270
|
+
## Validation status
|
|
271
|
+
|
|
272
|
+
Validated on 2026-09-11 with macOS 26.5.1, Apple Silicon, and Python 3.14.5:
|
|
273
|
+
|
|
274
|
+
- 88 automated tests passed, covering protocol parsing, time units, control
|
|
275
|
+
arguments, timeouts, cancellation, stderr, backpressure, and process cleanup.
|
|
276
|
+
Playback controls were tested using simulated helpers.
|
|
277
|
+
- A universal2 wheel was built from the sdist and installed offline in a new
|
|
278
|
+
virtual environment outside the repository. The read example and a read-only
|
|
279
|
+
subscription ran from that installation.
|
|
280
|
+
- Installed resources were checked for both architectures, the macOS 11.0
|
|
281
|
+
deployment target, framework signature, system library dependencies, resource
|
|
282
|
+
lookup, and wheel RECORD integrity.
|
|
283
|
+
- Live reads returned a nonempty Now Playing snapshot, the stream received
|
|
284
|
+
updates, and cancellation reaped the helper. No real playback controls or
|
|
285
|
+
upstream `test` command were executed.
|
|
286
|
+
- Interactive Intel desktops, real playback controls, a player compatibility
|
|
287
|
+
matrix, Developer ID signing, and notarized distribution remain unverified.
|
|
288
|
+
See the CI workflow below for automated Python and hosted macOS coverage.
|
|
289
|
+
|
|
290
|
+
`examples/verify_installed.py` performs the installed-wheel read-only audit. It
|
|
291
|
+
does not send playback controls or print actual track metadata values.
|
|
292
|
+
|
|
293
|
+
## License and release status
|
|
294
|
+
|
|
295
|
+
The wrapper uses BSD-3-Clause; see [LICENSE](https://github.com/Mai-icy/macos-mediaremote-python/blob/main/LICENSE).
|
|
296
|
+
Upstream copyright belongs to Jonas van den Berg and contributors. Its original
|
|
297
|
+
license is preserved in [licenses/mediaremote-adapter.txt](https://github.com/Mai-icy/macos-mediaremote-python/blob/main/licenses/mediaremote-adapter.txt)
|
|
298
|
+
and bundled with the wheel. The wrapper is independently implemented against the
|
|
299
|
+
public CLI/JSON protocol.
|
|
300
|
+
|
|
301
|
+
Framework layout changes, install-name changes, and re-signing are packaging
|
|
302
|
+
steps performed by this project, not official upstream artifacts.
|
|
303
|
+
|
|
304
|
+
Version `0.1.0a1` is an alpha release. The public API may change before a stable
|
|
305
|
+
release. See the support and validation sections above for the tested platforms
|
|
306
|
+
and remaining limitations.
|
|
307
|
+
|
|
308
|
+
## CI and release workflow
|
|
309
|
+
|
|
310
|
+
`.github/workflows/release.yml` runs on pushes to `main`, pull requests, and
|
|
311
|
+
manual dispatch. Ordinary pushes and pull requests never upload to PyPI.
|
|
312
|
+
|
|
313
|
+
The workflow tests Python 3.11 through 3.14, builds a universal2 wheel from the
|
|
314
|
+
sdist, runs `twine check --strict`, and installs that same wheel in fresh
|
|
315
|
+
Apple Silicon and Intel macOS 15 environments on Python 3.11 and 3.14. Runtime
|
|
316
|
+
tests run outside the source checkout. The installed-wheel audit verifies
|
|
317
|
+
resources, signatures, deployment targets, library dependencies, and a read-only
|
|
318
|
+
`get()` call. A successful empty read on a hosted runner is not evidence of live
|
|
319
|
+
player compatibility; desktop playback and streaming still need separate tests.
|
|
320
|
+
|
|
321
|
+
`release_checks.py` rejects inconsistent package versions, upstream pins, and
|
|
322
|
+
architecture settings. The wheel tag is derived from the locked deployment
|
|
323
|
+
target. You can run the metadata checks locally without invoking the helper:
|
|
324
|
+
|
|
325
|
+
```sh
|
|
326
|
+
python release_checks.py
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Publishing uses PyPI Trusted Publishing with these identifiers:
|
|
330
|
+
|
|
331
|
+
| Setting | Value |
|
|
332
|
+
| --- | --- |
|
|
333
|
+
| Project | `macos-mediaremote-python` |
|
|
334
|
+
| GitHub owner | `Mai-icy` |
|
|
335
|
+
| Repository | `macos-mediaremote-python` |
|
|
336
|
+
| Workflow filename | `release.yml` |
|
|
337
|
+
| GitHub environment | `pypi` |
|
|
338
|
+
|
|
339
|
+
For an authorized release, create a `vVERSION` tag matching the package version,
|
|
340
|
+
then manually dispatch this workflow at that tag with `publish=true`. The input
|
|
341
|
+
defaults to false. Publishing requires all checks to pass and approval in the
|
|
342
|
+
`pypi` environment. Only the publishing job receives OIDC permissions; it uploads
|
|
343
|
+
the already-tested artifacts without rebuilding. No persistent PyPI token or
|
|
344
|
+
TestPyPI workflow is used. Creating or pushing a tag alone does not upload a
|
|
345
|
+
release.
|