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

5.1 KiB

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

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:

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

use ProPresenter\Parser\GroupsFileReader;

$library = GroupsFileReader::read('/Users/me/.../Groups');

Throws InvalidArgumentException for missing files and RuntimeException for empty / unreadable files.


Writing

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.

$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

$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

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.