propresenter-php/doc/api/groups.md
Thorsten Bus 9e3e719806 feat(library): add readers + writers for all ProPresenter global libraries and theme bundles
Add full IO support for every global ProPresenter library file plus
theme folders, and extend the existing Labels/Macros readers with
exporters and editable accessors so every supported document is now a
round-trippable, mutable object.

New library readers/writers (each: FileReader, FileWriter, Library
wrapper, element wrapper where applicable, CLI tool, tests, doc/api/*.md):

- Groups          (ProGroupsDocument)        + GroupDefinition
- ClearGroups     (ClearGroupsDocument)      + ClearGroupDefinition
- CCLI            (CCLIDocument)
- Messages        (MessageDocument)          + Message
- Timers          (TimersDocument + Clock)   + Timer
- Stage           (Stage.Document)           + StageLayout
- Workspace       (ProPresenterWorkspace)    + Screen
- Props           (PropDocument)             + Prop
- TestPatterns    (TestPatternDocument)
- Calendar        (new CalendarDocument)     + CalendarEvent
- KeyMappings     (new KeyMappingsDocument)  + KeyMapping
- CommunicationDevices (JSON file)           + CommunicationDevice
- Theme bundles   (Template.Document folder + Assets/) + ThemeBundle/Slide/Asset

Extensions to existing modules:

- LabelsFileWriter; Label and LabelLibrary gain setters, addLabel,
  removeLabel, setColor / setColorHex helpers
- MacrosFileWriter; Macro/MacroCollection/MacroLibrary gain UUID, name,
  color, image_type, image_data, trigger_on_startup setters plus
  add/remove for macros and collections

Two new minimal proto schemas were defined for documents that lacked
upstream definitions:

- proto/calendar.proto   - CalendarDocument with Event entries, raw
  bytes for the action/macro sub-messages so the schema can evolve
- proto/keyMappings.proto - KeyMappingsDocument with ApplicationInfo
  and a forward-looking Mapping message (sample only carries the info)

The Theme file turned out to be a regular Rv\Data\Template\Document, so
no new proto was required for theme content; ThemeBundle layers folder
+ Assets/ handling on top in the same spirit as PresentationBundle.

GroupDefinition is intentionally distinct from the existing Group class
(which wraps song-level CueGroup) to avoid breaking song APIs.

Verified with the full PHPUnit suite: 370 tests, 9200 assertions, all
green; LSP diagnostics clean across src/. The unmodified reference
samples for Labels, Groups, ClearGroups, TestPatterns, Calendar and
KeyMappings round-trip byte-for-byte; the others round-trip with the
same byte length (PHP protobuf is not canonically deterministic but
re-write-after-write stabilises).

doc/INDEX.md, doc/keywords.md and AGENTS.md updated so every new module
is discoverable from the top level.
2026-05-03 21:40:09 +02:00

173 lines
5.1 KiB
Markdown

# Groups Library API
> PHP module for the global ProPresenter `Groups` file (raw protobuf, no
> extension). Exposes every named group definition (UUID, name, color,
> hot key) used to organise slides across songs and presentations.
## Quick Reference
```php
use ProPresenter\Parser\GroupsFileReader;
use ProPresenter\Parser\GroupsFileWriter;
$library = GroupsFileReader::read('/path/to/Groups');
foreach ($library->getGroups() as $group) {
$group->getName(); // "Verse 1"
$group->getUuid(); // "1D85C82C-EC82-44D8-8ED0-7742D46242C0"
$group->getColorHex(); // "#0077CC" | null
}
$library->addGroup('Bridge', '...uuid...');
GroupsFileWriter::write($library, '/path/to/Groups');
```
---
## File Layout
The `Groups` file is the protobuf-serialised
[`ProGroupsDocument`](../../proto/groups.proto):
| Field | Type | Description |
|-------|------|-------------|
| `groups` | repeated `Group` | Library group definitions (UUID, name, color, hotKey) |
Each `Group` carries:
| Field | Type | Description |
|-------|------|-------------|
| `uuid` | `UUID` | Stable identifier referenced by song-level cue groups |
| `name` | string | Display name (e.g. "Verse 1") |
| `color` | `Color` (optional) | RGBA float channels |
| `hotKey` | `HotKey` (optional) | Keyboard shortcut binding |
| `application_group_identifier` | `UUID` (optional) | Parent application group |
| `application_group_name` | string (optional) | Parent application group name |
Groups are identified by UUID; names should be unique but the format does
not enforce it.
---
## Reading
```php
use ProPresenter\Parser\GroupsFileReader;
$library = GroupsFileReader::read('/Users/me/.../Groups');
```
Throws `InvalidArgumentException` for missing files and `RuntimeException`
for empty / unreadable files.
---
## Writing
```php
use ProPresenter\Parser\GroupsFileWriter;
GroupsFileWriter::write($library, '/Users/me/.../Groups');
```
The writer serialises the underlying `ProGroupsDocument` back to bytes and
saves them. The unmodified reference sample round-trips byte-for-byte.
---
## GroupLibrary
Top-level wrapper around `Rv\Data\ProGroupsDocument`. Indexes groups by
UUID (case-insensitive) and by name for fast lookup.
```php
$library->getGroups(); // GroupDefinition[]
$library->count(); // int
$library->getGroupByUuid('1D85C82C-...'); // ?GroupDefinition (case-insensitive)
$library->getGroupByName('Verse 1'); // ?GroupDefinition
$library->addGroup('Bridge', '...uuid...'); // GroupDefinition
$library->removeGroup('...uuid...'); // bool
$library->getDocument(); // \Rv\Data\ProGroupsDocument
```
If the same UUID or name appears more than once the first occurrence wins
for lookups; every entry is preserved in `getGroups()` in document order.
---
## GroupDefinition
```php
$group->getUuid(); // "1D85C82C-EC82-44D8-8ED0-7742D46242C0"
$group->setUuid('...'); // self
$group->getName(); // "Verse 1"
$group->setName('Verse 2'); // self
$group->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
$group->getColorHex(); // "#0077CC" | null
$group->setColor(['r'=>1, 'g'=>0, 'b'=>0]); // self
$group->getHotKey(); // ?\Rv\Data\HotKey
$group->getApplicationGroupName(); // string
$group->getApplicationGroupUuid(); // string
$group->getProto(); // \Rv\Data\Group (raw protobuf)
```
The `GroupDefinition` class name is intentionally distinct from the
existing `Group` class which wraps song-level `CueGroup` objects (slide
references, not library definitions).
---
## CLI Tool
```bash
php bin/parse-groups.php /path/to/Groups
```
Output:
```
Groups (29):
[1] Vers :: 4E9D56A2-7E96-4975-97CC-44982257EF8A :: #0077CC
[2] Verse 1 :: 1D85C82C-EC82-44D8-8ED0-7742D46242C0 :: #0077CC
...
```
---
## Key Files
| File | Purpose |
|------|---------|
| `src/GroupLibrary.php` | Document-level wrapper with name / UUID lookup |
| `src/GroupDefinition.php` | Single library group (distinct from `Group` / `CueGroup`) |
| `src/GroupsFileReader.php` | Reads the `Groups` file |
| `src/GroupsFileWriter.php` | Writes the `Groups` file |
| `bin/parse-groups.php` | CLI tool |
| `proto/groups.proto` | Protobuf schema |
| `generated/Rv/Data/ProGroupsDocument.php` | Generated message class |
| `generated/Rv/Data/Group.php` | Generated group message class |
---
## Naming Disambiguation
The codebase has two `Group`-shaped classes for two different scopes:
| Class | Scope | Wraps |
|-------|-------|-------|
| `Group` | Song-level slide collection | `Rv\Data\Presentation\CueGroup` |
| `GroupDefinition` | Library-level group definition | `Rv\Data\Group` |
Songs reference library groups by UUID. The two classes co-exist because
ProPresenter's data model has the same name in both places.
---
## Scope Notes
This module covers reading and writing the `Groups` document. Wiring up
hot keys to actions and editing application group hierarchies are out of
scope; reach for `getHotKey()` / `getProto()` to inspect them when needed.