gukebox 1.0.0.dev7__tar.gz → 1.0.0.dev9__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.
Files changed (114) hide show
  1. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/PKG-INFO +32 -58
  2. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/README.md +30 -56
  3. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/api/current_tag_router.py +9 -6
  4. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/api/discs_router.py +5 -2
  5. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/api_controller.py +3 -3
  6. gukebox-1.0.0.dev9/discstore/adapters/inbound/ui_controller.py +511 -0
  7. gukebox-1.0.0.dev9/discstore/adapters/inbound/ui_pages/library.py +375 -0
  8. gukebox-1.0.0.dev9/discstore/adapters/inbound/ui_pages/settings.py +523 -0
  9. gukebox-1.0.0.dev9/discstore/adapters/inbound/ui_pages/sonos.py +502 -0
  10. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/commands.py +1 -32
  11. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/edit_disc.py +2 -2
  12. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/inbound/config.py +5 -20
  13. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/admin/app.py +0 -6
  14. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/admin/cli_presentation.py +0 -40
  15. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/admin/command_handlers.py +0 -11
  16. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/admin/commands.py +1 -6
  17. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/admin/di_container.py +2 -5
  18. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/app.py +1 -1
  19. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/entities.py +1 -6
  20. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/resolve.py +6 -16
  21. gukebox-1.0.0.dev9/jukebox/shared/__init__.py +0 -0
  22. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/shared/config_utils.py +0 -13
  23. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/sonos/selection.py +1 -6
  24. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/pyproject.toml +2 -3
  25. gukebox-1.0.0.dev7/discstore/adapters/inbound/config.py +0 -250
  26. gukebox-1.0.0.dev7/discstore/adapters/inbound/ui_controller.py +0 -1084
  27. gukebox-1.0.0.dev7/discstore/app.py +0 -105
  28. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/LICENSE +0 -0
  29. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/__init__.py +0 -0
  30. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/__init__.py +0 -0
  31. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/__init__.py +0 -0
  32. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/api/__init__.py +0 -0
  33. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/api/models.py +0 -0
  34. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/api/settings_router.py +0 -0
  35. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/cli_controller.py +0 -0
  36. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/cli_display.py +0 -0
  37. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/inbound/interactive_cli_controller.py +0 -0
  38. {gukebox-1.0.0.dev7/discstore/adapters/outbound → gukebox-1.0.0.dev9/discstore/adapters/inbound/ui_pages}/__init__.py +0 -0
  39. {gukebox-1.0.0.dev7/discstore/domain → gukebox-1.0.0.dev9/discstore/adapters/outbound}/__init__.py +0 -0
  40. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/outbound/json_library_adapter.py +0 -0
  41. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/adapters/outbound/text_current_tag_adapter.py +0 -0
  42. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/command_handlers.py +0 -0
  43. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/di_container.py +0 -0
  44. {gukebox-1.0.0.dev7/jukebox → gukebox-1.0.0.dev9/discstore/domain}/__init__.py +0 -0
  45. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/entities/__init__.py +0 -0
  46. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/entities/current_tag_status.py +0 -0
  47. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/repositories/__init__.py +0 -0
  48. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/__init__.py +0 -0
  49. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/add_disc.py +0 -0
  50. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/get_current_tag_status.py +0 -0
  51. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/get_disc.py +0 -0
  52. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/list_discs.py +0 -0
  53. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/remove_disc.py +0 -0
  54. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/resolve_tag_id.py +0 -0
  55. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/discstore/domain/use_cases/search_discs.py +0 -0
  56. {gukebox-1.0.0.dev7/jukebox/adapters → gukebox-1.0.0.dev9/jukebox}/__init__.py +0 -0
  57. {gukebox-1.0.0.dev7/jukebox/adapters/inbound → gukebox-1.0.0.dev9/jukebox/adapters}/__init__.py +0 -0
  58. {gukebox-1.0.0.dev7/jukebox/adapters/outbound → gukebox-1.0.0.dev9/jukebox/adapters/inbound}/__init__.py +0 -0
  59. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/inbound/cli_controller.py +0 -0
  60. {gukebox-1.0.0.dev7/jukebox/adapters/outbound/players → gukebox-1.0.0.dev9/jukebox/adapters/outbound}/__init__.py +0 -0
  61. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/outbound/json_library_adapter.py +0 -0
  62. {gukebox-1.0.0.dev7/jukebox/adapters/outbound/readers → gukebox-1.0.0.dev9/jukebox/adapters/outbound/players}/__init__.py +0 -0
  63. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/outbound/players/dryrun_player_adapter.py +0 -0
  64. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/outbound/players/sonos_player_adapter.py +0 -0
  65. {gukebox-1.0.0.dev7/jukebox/domain → gukebox-1.0.0.dev9/jukebox/adapters/outbound/readers}/__init__.py +0 -0
  66. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/outbound/readers/dryrun_reader_adapter.py +0 -0
  67. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/outbound/readers/pn532_reader_adapter.py +0 -0
  68. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/outbound/sonos_discovery_adapter.py +0 -0
  69. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/adapters/outbound/text_current_tag_adapter.py +0 -0
  70. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/admin/__init__.py +0 -0
  71. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/admin/services.py +0 -0
  72. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/di_container.py +0 -0
  73. {gukebox-1.0.0.dev7/jukebox/shared → gukebox-1.0.0.dev9/jukebox/domain}/__init__.py +0 -0
  74. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/entities/__init__.py +0 -0
  75. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/entities/current_tag_action.py +0 -0
  76. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/entities/disc.py +0 -0
  77. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/entities/library.py +0 -0
  78. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/entities/playback_action.py +0 -0
  79. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/entities/playback_session.py +0 -0
  80. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/entities/tag_event.py +0 -0
  81. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/ports/__init__.py +0 -0
  82. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/ports/player_port.py +0 -0
  83. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/ports/reader_port.py +0 -0
  84. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/repositories/__init__.py +0 -0
  85. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/repositories/current_tag_repository.py +0 -0
  86. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/repositories/library_repository.py +0 -0
  87. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/use_cases/__init__.py +0 -0
  88. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/use_cases/determine_action.py +0 -0
  89. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/use_cases/determine_current_tag_action.py +0 -0
  90. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/domain/use_cases/handle_tag_event.py +0 -0
  91. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/__init__.py +0 -0
  92. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/definitions.py +0 -0
  93. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/dict_utils.py +0 -0
  94. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/errors.py +0 -0
  95. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/file_settings_repository.py +0 -0
  96. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/migration.py +0 -0
  97. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/repositories.py +0 -0
  98. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/runtime_resolver.py +0 -0
  99. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/runtime_validation.py +0 -0
  100. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/selected_sonos_group_repository.py +0 -0
  101. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/service_protocols.py +0 -0
  102. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/timing_validation.py +0 -0
  103. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/types.py +0 -0
  104. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/validation_rules.py +0 -0
  105. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/settings/view_utils.py +0 -0
  106. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/shared/dependency_messages.py +0 -0
  107. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/shared/logger.py +0 -0
  108. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/shared/timing.py +0 -0
  109. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/sonos/__init__.py +0 -0
  110. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/sonos/discovery.py +0 -0
  111. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/jukebox/sonos/service.py +0 -0
  112. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/pn532/__init__.py +0 -0
  113. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/pn532/pn532.py +0 -0
  114. {gukebox-1.0.0.dev7 → gukebox-1.0.0.dev9}/pn532/spi.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gukebox
3
- Version: 1.0.0.dev7
3
+ Version: 1.0.0.dev9
4
4
  Summary: A Jukebox to play music on speakers using 'CD' with NFC tag
5
- Keywords: jukebox,discstore,music,nfc
5
+ Keywords: jukebox,music,nfc
6
6
  Author: Gudsfile
7
7
  License: MIT License
8
8
 
@@ -58,8 +58,9 @@ Description-Content-Type: text/markdown
58
58
 
59
59
  # Jukebox \[gukebox\]
60
60
 
61
- [![python versions](https://img.shields.io/pypi/pyversions/gukebox.svg)](https://pypi.python.org/pypi/gukebox)
62
- [![gukebox last version](https://img.shields.io/pypi/v/gukebox.svg)](https://pypi.python.org/pypi/gukebox)
61
+ ![rpi](https://img.shields.io/badge/-Zero%202W%20%7C%203%20%7C%205-C51A4A?logo=raspberry-pi&label=RPi&logoColor=C51A4A&labcolor=C51A4A)
62
+ [![python versions](https://img.shields.io/pypi/pyversions/gukebox.svg?logo=python)](https://pypi.python.org/pypi/gukebox)
63
+ [![gukebox last version](https://img.shields.io/pypi/v/gukebox.svg?logo=pypi)](https://pypi.python.org/pypi/gukebox)
63
64
  [![license](https://img.shields.io/pypi/l/gukebox.svg)](https://pypi.python.org/pypi/gukebox)
64
65
  [![actions status](https://github.com/gudsfile/jukebox/actions/workflows/python.yml/badge.svg)](https://github.com/gudsfile/jukebox/actions)
65
66
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
@@ -70,7 +71,7 @@ Description-Content-Type: text/markdown
70
71
 
71
72
  🚧 At the moment:
72
73
 
73
- - NFC tags - CDs must be pre-populated in a JSON file (`discstore` included with `jukebox` may be of help to you)
74
+ - NFC tags - CDs must be pre-populated in a JSON file (`jukebox-admin` included with `jukebox` may be of help to you)
74
75
  - supports many music providers (Spotify, Apple Music, etc.), just add the URIs to the JSON file
75
76
  - only works with Sonos speakers (there is a "dryrun" player for development), but code is designed to **add new ones**
76
77
  - **as soon as** the NFC tag is removed, the music pauses, then resumes when the NFC tag is replaced
@@ -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
- - [Readers](#readers)
90
- - [Players](#players)
89
+ - [Readers](#readers)
90
+ - [Players](#players)
91
91
  - [The library file](#the-library-file)
92
92
  - [Developer setup](#developer-setup)
93
93
 
@@ -161,18 +161,18 @@ uv sync
161
161
 
162
162
  ## First steps
163
163
 
164
- Initialize the library file with `discstore` or manually create it at `~/.config/jukebox/library.json`.
164
+ Initialize the library file with `jukebox-admin` or manually create it at `~/.config/jukebox/library.json`.
165
165
 
166
- ### Manage the library with the discstore
166
+ ### Manage the library with the Admin CLI
167
167
 
168
168
  To associate an URI with an NFC tag:
169
169
 
170
170
  ```shell
171
- discstore add tag_id --uri /path/to/media.mp3
171
+ jukebox-admin library add tag_id --uri /path/to/media.mp3
172
172
  ```
173
173
  or to pull the `tag_id` currently on the reader:
174
174
  ```shell
175
- discstore add --from-current --uri /path/to/media.mp3
175
+ jukebox-admin library add --from-current --uri /path/to/media.mp3
176
176
  ```
177
177
 
178
178
  Other commands are available, use `--help` to see them.
@@ -204,8 +204,6 @@ uv run --extra api jukebox-admin api
204
204
  uv run --extra ui jukebox-admin ui
205
205
  ```
206
206
 
207
- `discstore settings ...`, `discstore api`, and `discstore ui` remain available as compatibility commands, but `jukebox-admin` is the preferred CLI for admin flows.
208
-
209
207
  ### Manage the library manually
210
208
 
211
209
  Complete your `~/.config/jukebox/library.json` file with each tag id and the expected media URI.
@@ -216,16 +214,18 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
216
214
  Start the jukebox with the `jukebox` command (show help message with `--help`)
217
215
 
218
216
  ```shell
219
- jukebox PLAYER_TO_USE READER_TO_USE
217
+ jukebox --player PLAYER --reader READER
220
218
  ```
221
219
 
222
220
  🎉 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
221
 
224
- Optional Parameters
222
+ **Optional Parameters**
225
223
 
226
224
  | Parameter | Description |
227
225
  | --- | --- |
228
226
  | `--help` | Show help message. |
227
+ | `--player PLAYER` | Player to use (`sonos`, `dryrun`). |
228
+ | `--reader READER` | Reader to use (`pn532`, `dryrun`). |
229
229
  | `--library` | Path to the library file, default: `~/.config/jukebox/library.json`. |
230
230
  | `--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
231
  | `--pause-duration SECONDS` | Maximum duration of a pause before resetting the queue. Default: 900 seconds (15 minutes). |
@@ -234,40 +234,29 @@ Optional Parameters
234
234
 
235
235
  ### Readers
236
236
 
237
- **Dry run** (`dryrun`)
238
- Read a text entry.
239
- Allows you to simulate reading an NFC tag by writting the tag id in the console.
240
- Expected syntax: `tag_id` or `tag_id duration_seconds`.
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`
237
+ | Name | Description |
238
+ | --- | --- |
239
+ | Dry Run (`dryrun`) | Simulates NFC tag reading via stdin. Input format: `tag_id` or `tag_id duration_seconds`. |
240
+ | 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
241
 
245
- **NFC Pn532** (`pn532`)
246
- Read an NFC tag and get its UID.
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`.
242
+ > [!NOTE]
243
+ > See [docs/readers.md](docs/readers.md) for full setup, hardware requirements, and settings reference.
250
244
 
251
245
  ### Players
252
246
 
253
- **Dry run** (`dryrun`)
254
- Displays the events that a real speaker would have performed (`playing …`, `pause`, etc.).
255
-
256
- **Sonos** (`sonos`) [![SoCo](https://img.shields.io/badge/based%20on-SoCo-000)](https://github.com/SoCo/SoCo)
257
- Play music through a Sonos speaker.
258
- Three ways to select the speaker (mutually exclusive):
247
+ | Name | Description |
248
+ | --- | --- |
249
+ | Dry Run (`dryrun`) | Displays the events that a real speaker would have performed (`playing …`, `pause`, etc.). |
250
+ | Sonos (`sonos`) | [![SoCo](https://img.shields.io/badge/based%20on-SoCo-000)](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
251
 
260
- | Option | CLI flag | Environment variable | Behaviour |
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 |
252
+ > [!NOTE]
253
+ > See [docs/players.md](docs/players.md) for the full configuration reference.
265
254
 
266
255
  ## The library file
267
256
 
268
257
  The `library.json` file is a JSON file that contains the artists, albums and tags.
269
258
  It is used by the `jukebox` command to find the corresponding metadata for each tag.
270
- And the `discstore` command help you to managed this file with a CLI, an interactive CLI, an API or an UI (see `discstore --help`).
259
+ And the `jukebox-admin library` command help you to managed this file with a CLI, an interactive CLI, an API or an UI (see `jukebox-admin --help`).
271
260
 
272
261
  By default, this file should be placed at `~/.config/jukebox/library.json`. But you can use another path by creating a `JUKEBOX_LIBRARY_PATH` environment variable or with the `--library` argument.
273
262
 
@@ -341,9 +330,7 @@ uv sync
341
330
 
342
331
  Add `--all-extras` to install dependencies for all extras (`api` and `ui`).
343
332
 
344
- If needed, set `JUKEBOX_SONOS_HOST` (IP) or `JUKEBOX_SONOS_NAME` (speaker name) to select your Sonos speaker (see [Players](#players)).
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>`.
333
+ If needed, you can use a `.env` file and `uv run --env-file .env <command to run>`.
347
334
  A `.env.example` file is available, you can copy it and modify it to use it.
348
335
 
349
336
  Create a `library.json` file and complete it with the desired NFC tags and CDs.
@@ -354,12 +341,7 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
354
341
  Start the jukebox with `uv` and use `--help` to show help message
355
342
 
356
343
  ```shell
357
- uv run jukebox PLAYER_TO_USE READER_TO_USE
358
- ```
359
-
360
- Start the discstore `uv` and use `--help` to show help message
361
- ```shell
362
- uv run discstore --help
344
+ uv run jukebox --player PLAYER_TO_USE --reader READER_TO_USE
363
345
  ```
364
346
 
365
347
  Use `jukebox-admin` for admin commands:
@@ -375,15 +357,7 @@ uv run --extra api jukebox-admin api
375
357
  uv run --extra ui jukebox-admin ui
376
358
  ```
377
359
 
378
- Legacy compatibility commands remain available during the transition:
379
-
380
- ```shell
381
- uv run discstore settings show
382
- uv run --extra api discstore api
383
- uv run --extra ui discstore ui
384
- ```
385
-
386
- Other commands are available:
360
+ ### Development commands
387
361
 
388
362
  | Command | Description |
389
363
  | --- | --- |
@@ -1,7 +1,8 @@
1
1
  # Jukebox \[gukebox\]
2
2
 
3
- [![python versions](https://img.shields.io/pypi/pyversions/gukebox.svg)](https://pypi.python.org/pypi/gukebox)
4
- [![gukebox last version](https://img.shields.io/pypi/v/gukebox.svg)](https://pypi.python.org/pypi/gukebox)
3
+ ![rpi](https://img.shields.io/badge/-Zero%202W%20%7C%203%20%7C%205-C51A4A?logo=raspberry-pi&label=RPi&logoColor=C51A4A&labcolor=C51A4A)
4
+ [![python versions](https://img.shields.io/pypi/pyversions/gukebox.svg?logo=python)](https://pypi.python.org/pypi/gukebox)
5
+ [![gukebox last version](https://img.shields.io/pypi/v/gukebox.svg?logo=pypi)](https://pypi.python.org/pypi/gukebox)
5
6
  [![license](https://img.shields.io/pypi/l/gukebox.svg)](https://pypi.python.org/pypi/gukebox)
6
7
  [![actions status](https://github.com/gudsfile/jukebox/actions/workflows/python.yml/badge.svg)](https://github.com/gudsfile/jukebox/actions)
7
8
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
@@ -12,7 +13,7 @@
12
13
 
13
14
  🚧 At the moment:
14
15
 
15
- - NFC tags - CDs must be pre-populated in a JSON file (`discstore` included with `jukebox` may be of help to you)
16
+ - NFC tags - CDs must be pre-populated in a JSON file (`jukebox-admin` included with `jukebox` may be of help to you)
16
17
  - supports many music providers (Spotify, Apple Music, etc.), just add the URIs to the JSON file
17
18
  - only works with Sonos speakers (there is a "dryrun" player for development), but code is designed to **add new ones**
18
19
  - **as soon as** the NFC tag is removed, the music pauses, then resumes when the NFC tag is replaced
@@ -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
- - [Readers](#readers)
32
- - [Players](#players)
31
+ - [Readers](#readers)
32
+ - [Players](#players)
33
33
  - [The library file](#the-library-file)
34
34
  - [Developer setup](#developer-setup)
35
35
 
@@ -103,18 +103,18 @@ uv sync
103
103
 
104
104
  ## First steps
105
105
 
106
- Initialize the library file with `discstore` or manually create it at `~/.config/jukebox/library.json`.
106
+ Initialize the library file with `jukebox-admin` or manually create it at `~/.config/jukebox/library.json`.
107
107
 
108
- ### Manage the library with the discstore
108
+ ### Manage the library with the Admin CLI
109
109
 
110
110
  To associate an URI with an NFC tag:
111
111
 
112
112
  ```shell
113
- discstore add tag_id --uri /path/to/media.mp3
113
+ jukebox-admin library add tag_id --uri /path/to/media.mp3
114
114
  ```
115
115
  or to pull the `tag_id` currently on the reader:
116
116
  ```shell
117
- discstore add --from-current --uri /path/to/media.mp3
117
+ jukebox-admin library add --from-current --uri /path/to/media.mp3
118
118
  ```
119
119
 
120
120
  Other commands are available, use `--help` to see them.
@@ -146,8 +146,6 @@ uv run --extra api jukebox-admin api
146
146
  uv run --extra ui jukebox-admin ui
147
147
  ```
148
148
 
149
- `discstore settings ...`, `discstore api`, and `discstore ui` remain available as compatibility commands, but `jukebox-admin` is the preferred CLI for admin flows.
150
-
151
149
  ### Manage the library manually
152
150
 
153
151
  Complete your `~/.config/jukebox/library.json` file with each tag id and the expected media URI.
@@ -158,16 +156,18 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
158
156
  Start the jukebox with the `jukebox` command (show help message with `--help`)
159
157
 
160
158
  ```shell
161
- jukebox PLAYER_TO_USE READER_TO_USE
159
+ jukebox --player PLAYER --reader READER
162
160
  ```
163
161
 
164
162
  🎉 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
163
 
166
- Optional Parameters
164
+ **Optional Parameters**
167
165
 
168
166
  | Parameter | Description |
169
167
  | --- | --- |
170
168
  | `--help` | Show help message. |
169
+ | `--player PLAYER` | Player to use (`sonos`, `dryrun`). |
170
+ | `--reader READER` | Reader to use (`pn532`, `dryrun`). |
171
171
  | `--library` | Path to the library file, default: `~/.config/jukebox/library.json`. |
172
172
  | `--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
173
  | `--pause-duration SECONDS` | Maximum duration of a pause before resetting the queue. Default: 900 seconds (15 minutes). |
@@ -176,40 +176,29 @@ Optional Parameters
176
176
 
177
177
  ### Readers
178
178
 
179
- **Dry run** (`dryrun`)
180
- Read a text entry.
181
- Allows you to simulate reading an NFC tag by writting the tag id in the console.
182
- Expected syntax: `tag_id` or `tag_id duration_seconds`.
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`
179
+ | Name | Description |
180
+ | --- | --- |
181
+ | Dry Run (`dryrun`) | Simulates NFC tag reading via stdin. Input format: `tag_id` or `tag_id duration_seconds`. |
182
+ | 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
183
 
187
- **NFC Pn532** (`pn532`)
188
- Read an NFC tag and get its UID.
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`.
184
+ > [!NOTE]
185
+ > See [docs/readers.md](docs/readers.md) for full setup, hardware requirements, and settings reference.
192
186
 
193
187
  ### Players
194
188
 
195
- **Dry run** (`dryrun`)
196
- Displays the events that a real speaker would have performed (`playing …`, `pause`, etc.).
197
-
198
- **Sonos** (`sonos`) [![SoCo](https://img.shields.io/badge/based%20on-SoCo-000)](https://github.com/SoCo/SoCo)
199
- Play music through a Sonos speaker.
200
- Three ways to select the speaker (mutually exclusive):
189
+ | Name | Description |
190
+ | --- | --- |
191
+ | Dry Run (`dryrun`) | Displays the events that a real speaker would have performed (`playing …`, `pause`, etc.). |
192
+ | Sonos (`sonos`) | [![SoCo](https://img.shields.io/badge/based%20on-SoCo-000)](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
193
 
202
- | Option | CLI flag | Environment variable | Behaviour |
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 |
194
+ > [!NOTE]
195
+ > See [docs/players.md](docs/players.md) for the full configuration reference.
207
196
 
208
197
  ## The library file
209
198
 
210
199
  The `library.json` file is a JSON file that contains the artists, albums and tags.
211
200
  It is used by the `jukebox` command to find the corresponding metadata for each tag.
212
- And the `discstore` command help you to managed this file with a CLI, an interactive CLI, an API or an UI (see `discstore --help`).
201
+ And the `jukebox-admin library` command help you to managed this file with a CLI, an interactive CLI, an API or an UI (see `jukebox-admin --help`).
213
202
 
214
203
  By default, this file should be placed at `~/.config/jukebox/library.json`. But you can use another path by creating a `JUKEBOX_LIBRARY_PATH` environment variable or with the `--library` argument.
215
204
 
@@ -283,9 +272,7 @@ uv sync
283
272
 
284
273
  Add `--all-extras` to install dependencies for all extras (`api` and `ui`).
285
274
 
286
- If needed, set `JUKEBOX_SONOS_HOST` (IP) or `JUKEBOX_SONOS_NAME` (speaker name) to select your Sonos speaker (see [Players](#players)).
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>`.
275
+ If needed, you can use a `.env` file and `uv run --env-file .env <command to run>`.
289
276
  A `.env.example` file is available, you can copy it and modify it to use it.
290
277
 
291
278
  Create a `library.json` file and complete it with the desired NFC tags and CDs.
@@ -296,12 +283,7 @@ Take a look at `library.example.json` and the [The library file](#the-library-fi
296
283
  Start the jukebox with `uv` and use `--help` to show help message
297
284
 
298
285
  ```shell
299
- uv run jukebox PLAYER_TO_USE READER_TO_USE
300
- ```
301
-
302
- Start the discstore `uv` and use `--help` to show help message
303
- ```shell
304
- uv run discstore --help
286
+ uv run jukebox --player PLAYER_TO_USE --reader READER_TO_USE
305
287
  ```
306
288
 
307
289
  Use `jukebox-admin` for admin commands:
@@ -317,15 +299,7 @@ uv run --extra api jukebox-admin api
317
299
  uv run --extra ui jukebox-admin ui
318
300
  ```
319
301
 
320
- Legacy compatibility commands remain available during the transition:
321
-
322
- ```shell
323
- uv run discstore settings show
324
- uv run --extra api discstore api
325
- uv run --extra ui discstore ui
326
- ```
327
-
328
- Other commands are available:
302
+ ### Development commands
329
303
 
330
304
  | Command | Description |
331
305
  | --- | --- |
@@ -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, new_disc)
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, exclude_none=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, exclude_none=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, get_disc.execute(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, exclude_none=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, exclude_none=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:
@@ -23,7 +23,7 @@ except ModuleNotFoundError as e:
23
23
  if e.name != "fastapi":
24
24
  raise
25
25
  raise ModuleNotFoundError(
26
- optional_extra_dependency_message("The `api_controller` module", "api", "discstore api")
26
+ optional_extra_dependency_message("The `api_controller` module", "api", "jukebox-admin api")
27
27
  ) from e
28
28
  from discstore.domain.use_cases.add_disc import AddDisc
29
29
  from discstore.domain.use_cases.edit_disc import EditDisc
@@ -108,8 +108,8 @@ class APIController:
108
108
  self.settings_service = settings_service
109
109
  self.sonos_service = sonos_service
110
110
  self.app = FastAPI(
111
- title="DiscStore API",
112
- description="API for managing Jukebox disc library",
111
+ title="Jukebox Admin API",
112
+ description="API for managing Jukebox disc library and settings",
113
113
  docs_url="/docs",
114
114
  redoc_url="/redoc",
115
115
  )