gukebox 1.0.0.dev7__tar.gz → 1.0.0.dev8__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.
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/PKG-INFO +25 -36
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/README.md +24 -35
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/api/current_tag_router.py +9 -6
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/api/discs_router.py +5 -2
- gukebox-1.0.0.dev8/discstore/adapters/inbound/ui_controller.py +367 -0
- gukebox-1.0.0.dev8/discstore/adapters/inbound/ui_pages/library.py +370 -0
- gukebox-1.0.0.dev8/discstore/adapters/inbound/ui_pages/settings.py +519 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/app.py +0 -2
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/commands.py +1 -6
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/edit_disc.py +2 -2
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/inbound/config.py +5 -20
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/admin/app.py +0 -5
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/admin/commands.py +1 -6
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/admin/di_container.py +2 -5
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/app.py +1 -1
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/entities.py +1 -6
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/resolve.py +5 -15
- gukebox-1.0.0.dev8/jukebox/shared/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/shared/config_utils.py +0 -13
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/sonos/selection.py +1 -6
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/pyproject.toml +1 -1
- gukebox-1.0.0.dev7/discstore/adapters/inbound/ui_controller.py +0 -1084
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/LICENSE +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/api/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/api/models.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/api/settings_router.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/api_controller.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/cli_controller.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/cli_display.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/config.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/interactive_cli_controller.py +0 -0
- {gukebox-1.0.0.dev7/discstore/adapters/outbound → gukebox-1.0.0.dev8/discstore/adapters/inbound/ui_pages}/__init__.py +0 -0
- {gukebox-1.0.0.dev7/discstore/domain → gukebox-1.0.0.dev8/discstore/adapters/outbound}/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/outbound/json_library_adapter.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/outbound/text_current_tag_adapter.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/command_handlers.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/di_container.py +0 -0
- {gukebox-1.0.0.dev7/jukebox → gukebox-1.0.0.dev8/discstore/domain}/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/entities/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/entities/current_tag_status.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/repositories/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/add_disc.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/get_current_tag_status.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/get_disc.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/list_discs.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/remove_disc.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/resolve_tag_id.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/domain/use_cases/search_discs.py +0 -0
- {gukebox-1.0.0.dev7/jukebox/adapters → gukebox-1.0.0.dev8/jukebox}/__init__.py +0 -0
- {gukebox-1.0.0.dev7/jukebox/adapters/inbound → gukebox-1.0.0.dev8/jukebox/adapters}/__init__.py +0 -0
- {gukebox-1.0.0.dev7/jukebox/adapters/outbound → gukebox-1.0.0.dev8/jukebox/adapters/inbound}/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/inbound/cli_controller.py +0 -0
- {gukebox-1.0.0.dev7/jukebox/adapters/outbound/players → gukebox-1.0.0.dev8/jukebox/adapters/outbound}/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/outbound/json_library_adapter.py +0 -0
- {gukebox-1.0.0.dev7/jukebox/adapters/outbound/readers → gukebox-1.0.0.dev8/jukebox/adapters/outbound/players}/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/outbound/players/dryrun_player_adapter.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/outbound/players/sonos_player_adapter.py +0 -0
- {gukebox-1.0.0.dev7/jukebox/domain → gukebox-1.0.0.dev8/jukebox/adapters/outbound/readers}/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/outbound/readers/dryrun_reader_adapter.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/outbound/readers/pn532_reader_adapter.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/outbound/sonos_discovery_adapter.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/adapters/outbound/text_current_tag_adapter.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/admin/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/admin/cli_presentation.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/admin/command_handlers.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/admin/services.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/di_container.py +0 -0
- {gukebox-1.0.0.dev7/jukebox/shared → gukebox-1.0.0.dev8/jukebox/domain}/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/entities/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/entities/current_tag_action.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/entities/disc.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/entities/library.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/entities/playback_action.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/entities/playback_session.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/entities/tag_event.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/ports/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/ports/player_port.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/ports/reader_port.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/repositories/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/repositories/current_tag_repository.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/repositories/library_repository.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/use_cases/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/use_cases/determine_action.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/use_cases/determine_current_tag_action.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/domain/use_cases/handle_tag_event.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/definitions.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/dict_utils.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/errors.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/file_settings_repository.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/migration.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/repositories.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/runtime_resolver.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/runtime_validation.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/selected_sonos_group_repository.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/service_protocols.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/timing_validation.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/types.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/validation_rules.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/settings/view_utils.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/shared/dependency_messages.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/shared/logger.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/shared/timing.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/sonos/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/sonos/discovery.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/jukebox/sonos/service.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/pn532/__init__.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/pn532/pn532.py +0 -0
- {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/pn532/spi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: gukebox
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev8
|
|
4
4
|
Summary: A Jukebox to play music on speakers using 'CD' with NFC tag
|
|
5
5
|
Keywords: jukebox,discstore,music,nfc
|
|
6
6
|
Author: Gudsfile
|
|
@@ -58,8 +58,9 @@ Description-Content-Type: text/markdown
|
|
|
58
58
|
|
|
59
59
|
# Jukebox \[gukebox\]
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
[
|
|
62
|
+
[](https://pypi.python.org/pypi/gukebox)
|
|
63
|
+
[](https://pypi.python.org/pypi/gukebox)
|
|
63
64
|
[](https://pypi.python.org/pypi/gukebox)
|
|
64
65
|
[](https://github.com/gudsfile/jukebox/actions)
|
|
65
66
|
[](https://github.com/astral-sh/uv)
|
|
@@ -84,10 +85,9 @@ Description-Content-Type: text/markdown
|
|
|
84
85
|
|
|
85
86
|
- [Install](#install)
|
|
86
87
|
- [First steps](#first-steps)
|
|
87
|
-
- [Discstore](#manage-the-library-with-the-discstore)
|
|
88
88
|
- [Usage](#usage)
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
- [Readers](#readers)
|
|
90
|
+
- [Players](#players)
|
|
91
91
|
- [The library file](#the-library-file)
|
|
92
92
|
- [Developer setup](#developer-setup)
|
|
93
93
|
|
|
@@ -216,16 +216,18 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
|
|
|
216
216
|
Start the jukebox with the `jukebox` command (show help message with `--help`)
|
|
217
217
|
|
|
218
218
|
```shell
|
|
219
|
-
jukebox
|
|
219
|
+
jukebox --player PLAYER --reader READER
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
🎉 With choosing the `sonos` player and `pn532` reader, by approaching a NFC tag stored in the `library.json` file, you should hear the associated music begins.
|
|
223
223
|
|
|
224
|
-
Optional Parameters
|
|
224
|
+
**Optional Parameters**
|
|
225
225
|
|
|
226
226
|
| Parameter | Description |
|
|
227
227
|
| --- | --- |
|
|
228
228
|
| `--help` | Show help message. |
|
|
229
|
+
| `--player PLAYER` | Player to use (`sonos`, `dryrun`). |
|
|
230
|
+
| `--reader READER` | Reader to use (`pn532`, `dryrun`). |
|
|
229
231
|
| `--library` | Path to the library file, default: `~/.config/jukebox/library.json`. |
|
|
230
232
|
| `--pause-delay SECONDS` | Grace period before pausing when the NFC tag is removed. Fractional values such as `0.5` or `0.2` are supported, with a minimum of `0.2` seconds to avoid pausing on brief missed reads. Default: 0.25 seconds. |
|
|
231
233
|
| `--pause-duration SECONDS` | Maximum duration of a pause before resetting the queue. Default: 900 seconds (15 minutes). |
|
|
@@ -234,34 +236,23 @@ Optional Parameters
|
|
|
234
236
|
|
|
235
237
|
### Readers
|
|
236
238
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
- tag_id: the full identifier of the tag, in the format required by the system
|
|
242
|
-
- duration_seconds: a non-negative number of seconds used to simulate how long the tag remains in place. Fractional values are allowed.
|
|
243
|
-
Complete example: `your:tag:uid 2.5`
|
|
239
|
+
| Name | Description |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| Dry Run (`dryrun`) | Simulates NFC tag reading via stdin. Input format: `tag_id` or `tag_id duration_seconds`. |
|
|
242
|
+
| Pn532 NFC (`pn532`) | Reads physical NFC tags. Works with a **PN532** reader and **NTAG2xx** tags. Requires the `pn532` extra and SPI enabled on the Raspberry Pi. |
|
|
244
243
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
This project works with an NFC reader like the **PN532** and NFC tags like the **NTAG2xx**.
|
|
248
|
-
It is configured according to the [Waveshare PN532 wiki](https://www.waveshare.com/wiki/PN532_NFC_HAT).
|
|
249
|
-
Don't forget to enable the SPI interface using the command `sudo raspi-config`, then go to: `Interface Options > SPI > Enable > Yes`.
|
|
244
|
+
> [!NOTE]
|
|
245
|
+
> See [docs/readers.md](docs/readers.md) for full setup, hardware requirements, and settings reference.
|
|
250
246
|
|
|
251
247
|
### Players
|
|
252
248
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
Play music through a Sonos speaker.
|
|
258
|
-
Three ways to select the speaker (mutually exclusive):
|
|
249
|
+
| Name | Description |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| Dry Run (`dryrun`) | Displays the events that a real speaker would have performed (`playing …`, `pause`, etc.). |
|
|
252
|
+
| Sonos (`sonos`) | [](https://github.com/SoCo/SoCo) Plays music through a Sonos speaker. Select by IP (`--sonos-host`), by name (`--sonos-name`), or let it auto-discover. |
|
|
259
253
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
| By IP | `--sonos-host 192.168.0.x` | `JUKEBOX_SONOS_HOST` | Connect directly, no discovery |
|
|
263
|
-
| By name | `--sonos-name "Living Room"` | `JUKEBOX_SONOS_NAME` | Discover, then filter by name (case-sensitive) |
|
|
264
|
-
| Auto | *(omit both)* | *(omit both)* | Discover, pick the first speaker alphabetically |
|
|
254
|
+
> [!NOTE]
|
|
255
|
+
> See [docs/players.md](docs/players.md) for the full configuration reference.
|
|
265
256
|
|
|
266
257
|
## The library file
|
|
267
258
|
|
|
@@ -341,9 +332,7 @@ uv sync
|
|
|
341
332
|
|
|
342
333
|
Add `--all-extras` to install dependencies for all extras (`api` and `ui`).
|
|
343
334
|
|
|
344
|
-
If needed,
|
|
345
|
-
If neither is set, the jukebox will auto-discover a speaker on the network.
|
|
346
|
-
To do this you can use a `.env` file and `uv run --env-file .env <command to run>`.
|
|
335
|
+
If needed, you can use a `.env` file and `uv run --env-file .env <command to run>`.
|
|
347
336
|
A `.env.example` file is available, you can copy it and modify it to use it.
|
|
348
337
|
|
|
349
338
|
Create a `library.json` file and complete it with the desired NFC tags and CDs.
|
|
@@ -354,7 +343,7 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
|
|
|
354
343
|
Start the jukebox with `uv` and use `--help` to show help message
|
|
355
344
|
|
|
356
345
|
```shell
|
|
357
|
-
uv run jukebox PLAYER_TO_USE READER_TO_USE
|
|
346
|
+
uv run jukebox --player PLAYER_TO_USE --reader READER_TO_USE
|
|
358
347
|
```
|
|
359
348
|
|
|
360
349
|
Start the discstore `uv` and use `--help` to show help message
|
|
@@ -383,7 +372,7 @@ uv run --extra api discstore api
|
|
|
383
372
|
uv run --extra ui discstore ui
|
|
384
373
|
```
|
|
385
374
|
|
|
386
|
-
|
|
375
|
+
### Development commands
|
|
387
376
|
|
|
388
377
|
| Command | Description |
|
|
389
378
|
| --- | --- |
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Jukebox \[gukebox\]
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[
|
|
4
|
+
[](https://pypi.python.org/pypi/gukebox)
|
|
5
|
+
[](https://pypi.python.org/pypi/gukebox)
|
|
5
6
|
[](https://pypi.python.org/pypi/gukebox)
|
|
6
7
|
[](https://github.com/gudsfile/jukebox/actions)
|
|
7
8
|
[](https://github.com/astral-sh/uv)
|
|
@@ -26,10 +27,9 @@
|
|
|
26
27
|
|
|
27
28
|
- [Install](#install)
|
|
28
29
|
- [First steps](#first-steps)
|
|
29
|
-
- [Discstore](#manage-the-library-with-the-discstore)
|
|
30
30
|
- [Usage](#usage)
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
- [Readers](#readers)
|
|
32
|
+
- [Players](#players)
|
|
33
33
|
- [The library file](#the-library-file)
|
|
34
34
|
- [Developer setup](#developer-setup)
|
|
35
35
|
|
|
@@ -158,16 +158,18 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
|
|
|
158
158
|
Start the jukebox with the `jukebox` command (show help message with `--help`)
|
|
159
159
|
|
|
160
160
|
```shell
|
|
161
|
-
jukebox
|
|
161
|
+
jukebox --player PLAYER --reader READER
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
🎉 With choosing the `sonos` player and `pn532` reader, by approaching a NFC tag stored in the `library.json` file, you should hear the associated music begins.
|
|
165
165
|
|
|
166
|
-
Optional Parameters
|
|
166
|
+
**Optional Parameters**
|
|
167
167
|
|
|
168
168
|
| Parameter | Description |
|
|
169
169
|
| --- | --- |
|
|
170
170
|
| `--help` | Show help message. |
|
|
171
|
+
| `--player PLAYER` | Player to use (`sonos`, `dryrun`). |
|
|
172
|
+
| `--reader READER` | Reader to use (`pn532`, `dryrun`). |
|
|
171
173
|
| `--library` | Path to the library file, default: `~/.config/jukebox/library.json`. |
|
|
172
174
|
| `--pause-delay SECONDS` | Grace period before pausing when the NFC tag is removed. Fractional values such as `0.5` or `0.2` are supported, with a minimum of `0.2` seconds to avoid pausing on brief missed reads. Default: 0.25 seconds. |
|
|
173
175
|
| `--pause-duration SECONDS` | Maximum duration of a pause before resetting the queue. Default: 900 seconds (15 minutes). |
|
|
@@ -176,34 +178,23 @@ Optional Parameters
|
|
|
176
178
|
|
|
177
179
|
### Readers
|
|
178
180
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
- tag_id: the full identifier of the tag, in the format required by the system
|
|
184
|
-
- duration_seconds: a non-negative number of seconds used to simulate how long the tag remains in place. Fractional values are allowed.
|
|
185
|
-
Complete example: `your:tag:uid 2.5`
|
|
181
|
+
| Name | Description |
|
|
182
|
+
| --- | --- |
|
|
183
|
+
| Dry Run (`dryrun`) | Simulates NFC tag reading via stdin. Input format: `tag_id` or `tag_id duration_seconds`. |
|
|
184
|
+
| Pn532 NFC (`pn532`) | Reads physical NFC tags. Works with a **PN532** reader and **NTAG2xx** tags. Requires the `pn532` extra and SPI enabled on the Raspberry Pi. |
|
|
186
185
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
This project works with an NFC reader like the **PN532** and NFC tags like the **NTAG2xx**.
|
|
190
|
-
It is configured according to the [Waveshare PN532 wiki](https://www.waveshare.com/wiki/PN532_NFC_HAT).
|
|
191
|
-
Don't forget to enable the SPI interface using the command `sudo raspi-config`, then go to: `Interface Options > SPI > Enable > Yes`.
|
|
186
|
+
> [!NOTE]
|
|
187
|
+
> See [docs/readers.md](docs/readers.md) for full setup, hardware requirements, and settings reference.
|
|
192
188
|
|
|
193
189
|
### Players
|
|
194
190
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
Play music through a Sonos speaker.
|
|
200
|
-
Three ways to select the speaker (mutually exclusive):
|
|
191
|
+
| Name | Description |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| Dry Run (`dryrun`) | Displays the events that a real speaker would have performed (`playing …`, `pause`, etc.). |
|
|
194
|
+
| Sonos (`sonos`) | [](https://github.com/SoCo/SoCo) Plays music through a Sonos speaker. Select by IP (`--sonos-host`), by name (`--sonos-name`), or let it auto-discover. |
|
|
201
195
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
| By IP | `--sonos-host 192.168.0.x` | `JUKEBOX_SONOS_HOST` | Connect directly, no discovery |
|
|
205
|
-
| By name | `--sonos-name "Living Room"` | `JUKEBOX_SONOS_NAME` | Discover, then filter by name (case-sensitive) |
|
|
206
|
-
| Auto | *(omit both)* | *(omit both)* | Discover, pick the first speaker alphabetically |
|
|
196
|
+
> [!NOTE]
|
|
197
|
+
> See [docs/players.md](docs/players.md) for the full configuration reference.
|
|
207
198
|
|
|
208
199
|
## The library file
|
|
209
200
|
|
|
@@ -283,9 +274,7 @@ uv sync
|
|
|
283
274
|
|
|
284
275
|
Add `--all-extras` to install dependencies for all extras (`api` and `ui`).
|
|
285
276
|
|
|
286
|
-
If needed,
|
|
287
|
-
If neither is set, the jukebox will auto-discover a speaker on the network.
|
|
288
|
-
To do this you can use a `.env` file and `uv run --env-file .env <command to run>`.
|
|
277
|
+
If needed, you can use a `.env` file and `uv run --env-file .env <command to run>`.
|
|
289
278
|
A `.env.example` file is available, you can copy it and modify it to use it.
|
|
290
279
|
|
|
291
280
|
Create a `library.json` file and complete it with the desired NFC tags and CDs.
|
|
@@ -296,7 +285,7 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
|
|
|
296
285
|
Start the jukebox with `uv` and use `--help` to show help message
|
|
297
286
|
|
|
298
287
|
```shell
|
|
299
|
-
uv run jukebox PLAYER_TO_USE READER_TO_USE
|
|
288
|
+
uv run jukebox --player PLAYER_TO_USE --reader READER_TO_USE
|
|
300
289
|
```
|
|
301
290
|
|
|
302
291
|
Start the discstore `uv` and use `--help` to show help message
|
|
@@ -325,7 +314,7 @@ uv run --extra api discstore api
|
|
|
325
314
|
uv run --extra ui discstore ui
|
|
326
315
|
```
|
|
327
316
|
|
|
328
|
-
|
|
317
|
+
### Development commands
|
|
329
318
|
|
|
330
319
|
| Command | Description |
|
|
331
320
|
| --- | --- |
|
{gukebox-1.0.0.dev7 → gukebox-1.0.0.dev8}/discstore/adapters/inbound/api/current_tag_router.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Any, Optional
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, HTTPException, Response, status
|
|
4
|
+
from pydantic import ValidationError
|
|
4
5
|
|
|
5
6
|
from discstore.adapters.inbound.api.models import (
|
|
6
7
|
CurrentTagDiscOutput,
|
|
@@ -92,8 +93,8 @@ def build_current_tag_router(
|
|
|
92
93
|
|
|
93
94
|
try:
|
|
94
95
|
new_disc = Disc(**disc.model_dump())
|
|
95
|
-
add_disc.execute(current_tag_status.tag_id, new_disc)
|
|
96
|
-
return build_current_tag_disc_output(current_tag_status.tag_id,
|
|
96
|
+
created_disc = add_disc.execute(current_tag_status.tag_id, new_disc)
|
|
97
|
+
return build_current_tag_disc_output(current_tag_status.tag_id, created_disc)
|
|
97
98
|
except ValueError as value_err:
|
|
98
99
|
raise HTTPException(status_code=409, detail=str(value_err))
|
|
99
100
|
except Exception as err:
|
|
@@ -121,14 +122,16 @@ def build_current_tag_router(
|
|
|
121
122
|
try:
|
|
122
123
|
metadata = None
|
|
123
124
|
if disc_patch.metadata is not None:
|
|
124
|
-
metadata = DiscMetadata(**disc_patch.metadata.model_dump(exclude_unset=True
|
|
125
|
+
metadata = DiscMetadata(**disc_patch.metadata.model_dump(exclude_unset=True))
|
|
125
126
|
|
|
126
127
|
option = None
|
|
127
128
|
if disc_patch.option is not None:
|
|
128
|
-
option = DiscOption(**disc_patch.option.model_dump(exclude_unset=True
|
|
129
|
+
option = DiscOption(**disc_patch.option.model_dump(exclude_unset=True))
|
|
129
130
|
|
|
130
|
-
edit_disc.execute(current_tag_status.tag_id, disc_patch.uri, metadata, option)
|
|
131
|
-
return build_current_tag_disc_output(current_tag_status.tag_id,
|
|
131
|
+
updated_disc = edit_disc.execute(current_tag_status.tag_id, disc_patch.uri, metadata, option)
|
|
132
|
+
return build_current_tag_disc_output(current_tag_status.tag_id, updated_disc)
|
|
133
|
+
except ValidationError as err:
|
|
134
|
+
raise HTTPException(status_code=422, detail=err.errors())
|
|
132
135
|
except ValueError as value_err:
|
|
133
136
|
raise HTTPException(status_code=404, detail=str(value_err))
|
|
134
137
|
except Exception as err:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Dict
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, HTTPException, Response, status
|
|
4
|
+
from pydantic import ValidationError
|
|
4
5
|
|
|
5
6
|
from discstore.adapters.inbound.api.models import DiscInput, DiscOutput, DiscPatchInput
|
|
6
7
|
from discstore.domain.entities import Disc, DiscMetadata, DiscOption
|
|
@@ -48,13 +49,15 @@ def build_discs_router(
|
|
|
48
49
|
try:
|
|
49
50
|
metadata = None
|
|
50
51
|
if disc_patch.metadata is not None:
|
|
51
|
-
metadata = DiscMetadata(**disc_patch.metadata.model_dump(exclude_unset=True
|
|
52
|
+
metadata = DiscMetadata(**disc_patch.metadata.model_dump(exclude_unset=True))
|
|
52
53
|
|
|
53
54
|
option = None
|
|
54
55
|
if disc_patch.option is not None:
|
|
55
|
-
option = DiscOption(**disc_patch.option.model_dump(exclude_unset=True
|
|
56
|
+
option = DiscOption(**disc_patch.option.model_dump(exclude_unset=True))
|
|
56
57
|
|
|
57
58
|
return edit_disc.execute(tag_id, disc_patch.uri, metadata, option)
|
|
59
|
+
except ValidationError as err:
|
|
60
|
+
raise HTTPException(status_code=422, detail=err.errors())
|
|
58
61
|
except ValueError as value_err:
|
|
59
62
|
raise HTTPException(status_code=404, detail=str(value_err))
|
|
60
63
|
except Exception as err:
|