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.
This commit is contained in:
parent
4e1ac9b7ea
commit
9e3e719806
39
AGENTS.md
39
AGENTS.md
|
|
@ -19,8 +19,21 @@ All project documentation lives in `doc/`. Load only what you need.
|
|||
| Parse/modify `.pro` song files | `doc/api/song.md` |
|
||||
| Parse/modify `.proplaylist` files | `doc/api/playlist.md` |
|
||||
| Parse/modify `.probundle` files | `doc/api/bundle.md` |
|
||||
| Read the global `Macros` file | `doc/api/macros.md` |
|
||||
| Read the global `Labels` file | `doc/api/labels.md` |
|
||||
| Read/write the global `Macros` file | `doc/api/macros.md` |
|
||||
| Read/write the global `Labels` file | `doc/api/labels.md` |
|
||||
| Read/write the global `Groups` file | `doc/api/groups.md` |
|
||||
| Read/write the global `ClearGroups` file | `doc/api/clear-groups.md` |
|
||||
| Read/write the global `CCLI` file | `doc/api/ccli.md` |
|
||||
| Read/write the global `Messages` file | `doc/api/messages.md` |
|
||||
| Read/write the global `Timers` file | `doc/api/timers.md` |
|
||||
| Read/write the global `Stage` file | `doc/api/stage.md` |
|
||||
| Read/write the global `Workspace` file | `doc/api/workspace.md` |
|
||||
| Read/write the global `Props` file | `doc/api/props.md` |
|
||||
| Read/write the global `TestPatterns` file | `doc/api/test-patterns.md` |
|
||||
| Read/write the global `Calendar` file | `doc/api/calendar.md` |
|
||||
| Read/write the global `KeyMappings` file | `doc/api/key-mappings.md` |
|
||||
| Read/write the `CommunicationDevices` JSON file | `doc/api/communication-devices.md` |
|
||||
| Read/write a theme folder (Theme + Assets/) | `doc/api/theme.md` |
|
||||
| Understand `.pro` binary format | `doc/formats/pp_song_spec.md` |
|
||||
| Understand `.proplaylist` binary format | `doc/formats/pp_playlist_spec.md` |
|
||||
| Understand `.probundle` binary format | `doc/formats/pp_bundle_spec.md` |
|
||||
|
|
@ -57,16 +70,34 @@ PHP tools for parsing, modifying, and generating ProPresenter 7 files:
|
|||
- **Songs** (`.pro`) — Protobuf-encoded presentation files with lyrics, groups, slides, arrangements, translations
|
||||
- **Playlists** (`.proplaylist`) — ZIP64 archives containing playlist metadata and embedded songs
|
||||
- **Bundles** (`.probundle`) — ZIP archives containing a single presentation with embedded media assets
|
||||
- **Macros** (`Macros`, no extension) — Global protobuf-encoded macro library with collections
|
||||
- **Labels** (`Labels`, no extension) — Global protobuf-encoded label library (slide labels with optional UI colors)
|
||||
- **Themes** (folder with `Theme` + `Assets/`) — Template document plus media used as a slide theme
|
||||
- **Global library files** (no extension) — `Macros`, `Labels`, `Groups`, `ClearGroups`, `CCLI`, `Messages`, `Timers`, `Stage`, `Workspace`, `Props`, `TestPatterns`, `Calendar`, `KeyMappings` (protobuf) and `CommunicationDevices` (JSON)
|
||||
|
||||
### CLI Tools
|
||||
|
||||
```bash
|
||||
# Songs / playlists / bundles
|
||||
php bin/parse-song.php path/to/song.pro
|
||||
php bin/parse-playlist.php path/to/playlist.proplaylist
|
||||
|
||||
# Global library files (one parser per type)
|
||||
php bin/parse-macros.php path/to/Macros
|
||||
php bin/parse-labels.php path/to/Labels
|
||||
php bin/parse-groups.php path/to/Groups
|
||||
php bin/parse-clear-groups.php path/to/ClearGroups
|
||||
php bin/parse-ccli.php path/to/CCLI
|
||||
php bin/parse-messages.php path/to/Messages
|
||||
php bin/parse-timers.php path/to/Timers
|
||||
php bin/parse-stage.php path/to/Stage
|
||||
php bin/parse-workspace.php path/to/Workspace
|
||||
php bin/parse-props.php path/to/Props
|
||||
php bin/parse-test-patterns.php path/to/TestPatterns
|
||||
php bin/parse-calendar.php path/to/Calendar
|
||||
php bin/parse-key-mappings.php path/to/KeyMappings
|
||||
php bin/parse-communication-devices.php path/to/CommunicationDevices
|
||||
|
||||
# Theme folder
|
||||
php bin/parse-theme.php path/to/ThemeFolder
|
||||
```
|
||||
|
||||
### Key Source Files
|
||||
|
|
|
|||
26
bin/parse-calendar.php
Executable file
26
bin/parse-calendar.php
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\CalendarFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-calendar.php <Calendar>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$library = CalendarFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$events = $library->getEvents();
|
||||
echo 'Calendar events (' . count($events) . ') mode=' . $library->getMode() . ":\n";
|
||||
foreach ($events as $index => $event) {
|
||||
echo sprintf(" [%d] %s :: %s :: start=%s :: end=%s :: action=%dB :: macro=%dB\n", $index + 1, $event->getName() === '' ? '(unnamed)' : $event->getName(), $event->getUuid(), (string) ($event->getStartTimeSeconds() ?? ''), (string) ($event->getEndTimeSeconds() ?? ''), strlen($event->getActionData()), strlen($event->getMacroData()));
|
||||
}
|
||||
25
bin/parse-ccli.php
Normal file
25
bin/parse-ccli.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\CCLIFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-ccli.php <CCLI>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$filePath = $argv[1];
|
||||
|
||||
try {
|
||||
$library = CCLIFileReader::read($filePath);
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "CCLI (1):\n";
|
||||
echo sprintf(" [1] enabled=%s :: license=%s :: display_type=%d :: template=%s\n", $library->isCCLIDisplayEnabled() ? 'yes' : 'no', $library->getCCLILicense() === '' ? '(empty)' : $library->getCCLILicense(), $library->getDisplayType(), $library->getTemplate() === null ? 'no' : 'yes');
|
||||
35
bin/parse-clear-groups.php
Normal file
35
bin/parse-clear-groups.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\ClearGroupsFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-clear-groups.php <ClearGroups>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$filePath = $argv[1];
|
||||
|
||||
try {
|
||||
$library = ClearGroupsFileReader::read($filePath);
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$groups = $library->getGroups();
|
||||
|
||||
echo "ClearGroups (" . count($groups) . "):\n";
|
||||
foreach ($groups as $index => $group) {
|
||||
$number = $index + 1;
|
||||
$name = $group->getName();
|
||||
$displayName = $name === '' ? '(unnamed)' : $name;
|
||||
$uuid = $group->getUuid();
|
||||
$colorPart = $group->getColorHex() ?? '(no tint)';
|
||||
|
||||
echo sprintf(" [%d] %s :: %s :: image_type=%d :: %s\n", $number, $displayName, $uuid, $group->getImageType(), $colorPart);
|
||||
}
|
||||
29
bin/parse-communication-devices.php
Executable file
29
bin/parse-communication-devices.php
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\CommunicationDevicesFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-communication-devices.php <CommunicationDevices>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$library = CommunicationDevicesFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$devices = $library->getDevices();
|
||||
echo 'Communication devices (' . count($devices) . "):\n";
|
||||
if ($devices === []) {
|
||||
echo " (none configured)\n";
|
||||
}
|
||||
foreach ($devices as $index => $device) {
|
||||
echo sprintf(" [%d] %s :: %s :: %s :: %s\n", $index + 1, $device->getName() === '' ? '(unnamed)' : $device->getName(), $device->getId(), $device->getType(), $device->getAddress());
|
||||
}
|
||||
35
bin/parse-groups.php
Executable file
35
bin/parse-groups.php
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\GroupsFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-groups.php <Groups>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$filePath = $argv[1];
|
||||
|
||||
try {
|
||||
$library = GroupsFileReader::read($filePath);
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$groups = $library->getGroups();
|
||||
|
||||
echo "Groups (" . count($groups) . "):\n";
|
||||
foreach ($groups as $index => $group) {
|
||||
$number = $index + 1;
|
||||
$name = $group->getName();
|
||||
$displayName = $name === '' ? '(unnamed)' : $name;
|
||||
$uuid = $group->getUuid();
|
||||
$colorPart = $group->getColorHex() ?? '(no color)';
|
||||
|
||||
echo sprintf(" [%d] %s :: %s :: %s\n", $number, $displayName, $uuid, $colorPart);
|
||||
}
|
||||
35
bin/parse-key-mappings.php
Normal file
35
bin/parse-key-mappings.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\KeyMappingsFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-key-mappings.php <KeyMappings>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$filePath = $argv[1];
|
||||
|
||||
try {
|
||||
$library = KeyMappingsFileReader::read($filePath);
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$mappings = $library->getMappings();
|
||||
|
||||
echo "KeyMappings (" . count($mappings) . "):\n";
|
||||
foreach ($mappings as $index => $mapping) {
|
||||
$number = $index + 1;
|
||||
$name = $mapping->getName();
|
||||
$displayName = $name === '' ? '(unnamed)' : $name;
|
||||
$uuid = $mapping->getUuid();
|
||||
$hotKeyPart = $mapping->getHotKey() === null ? '(no hot key)' : 'hot_key=yes';
|
||||
|
||||
echo sprintf(" [%d] %s :: %s :: target_bytes=%d :: %s\n", $number, $displayName, $uuid, strlen($mapping->getTarget()), $hotKeyPart);
|
||||
}
|
||||
27
bin/parse-messages.php
Executable file
27
bin/parse-messages.php
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\MessagesFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-messages.php <Messages>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$library = MessagesFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$messages = $library->getMessages();
|
||||
echo 'Messages (' . count($messages) . "):\n";
|
||||
foreach ($messages as $index => $message) {
|
||||
$title = $message->getTitle() === '' ? '(untitled)' : $message->getTitle();
|
||||
echo sprintf(" [%d] %s :: %s :: clear=%d :: network=%s\n", $index + 1, $title, $message->getUuid(), $message->getClearType(), $message->isVisibleOnNetwork() ? 'yes' : 'no');
|
||||
}
|
||||
25
bin/parse-props.php
Normal file
25
bin/parse-props.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\PropsFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-props.php <Props>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$library = PropsFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo 'Props (' . $library->count() . "):\n";
|
||||
foreach ($library->getProps() as $index => $prop) {
|
||||
echo sprintf(" [%d] %s :: %s :: %s\n", $index + 1, $prop->getName() ?: '(unnamed)', $prop->getUuid(), $prop->isEnabled() ? 'enabled' : 'disabled');
|
||||
}
|
||||
25
bin/parse-stage.php
Normal file
25
bin/parse-stage.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\StageFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-stage.php <Stage>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$library = StageFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo 'Stage layouts (' . $library->count() . "):\n";
|
||||
foreach ($library->getLayouts() as $index => $layout) {
|
||||
echo sprintf(" [%d] %s :: %s\n", $index + 1, $layout->getName() ?: '(unnamed)', $layout->getUuid());
|
||||
}
|
||||
34
bin/parse-test-patterns.php
Normal file
34
bin/parse-test-patterns.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\TestPatternsFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-test-patterns.php <TestPatterns>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$filePath = $argv[1];
|
||||
|
||||
try {
|
||||
$library = TestPatternsFileReader::read($filePath);
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "TestPatterns (" . $library->count() . "):\n";
|
||||
echo sprintf(" State: selected=%s :: name=%s :: display_location=%d :: screen=%s\n", $library->getSelectedPatternUuid() === '' ? '(none)' : $library->getSelectedPatternUuid(), $library->getSelectedPatternNameLocalizationKey() === '' ? '(none)' : $library->getSelectedPatternNameLocalizationKey(), $library->getDisplayLocation(), $library->getSpecificScreenUuid() === '' ? '(none)' : $library->getSpecificScreenUuid());
|
||||
|
||||
foreach ($library->getPatterns() as $index => $pattern) {
|
||||
$number = $index + 1;
|
||||
$name = $pattern->getNameLocalizationKey();
|
||||
$displayName = $name === '' ? '(unnamed)' : $name;
|
||||
$uuid = $pattern->getUuid()?->getString() ?? '';
|
||||
|
||||
echo sprintf(" [%d] %s :: %s\n", $number, $displayName, $uuid);
|
||||
}
|
||||
30
bin/parse-theme.php
Normal file
30
bin/parse-theme.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\ThemeFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-theme.php <theme-folder>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$bundle = ThemeFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Theme folder: {$argv[1]}\n";
|
||||
echo 'Slides (' . $bundle->count() . "):\n";
|
||||
foreach ($bundle->getSlides() as $index => $slide) {
|
||||
echo sprintf(" [%d] %s\n", $index + 1, $slide->getName() ?: '(unnamed)');
|
||||
}
|
||||
echo 'Assets (' . $bundle->getAssetCount() . "):\n";
|
||||
foreach ($bundle->getAssets() as $index => $asset) {
|
||||
echo sprintf(" [%d] Assets/%s :: %d bytes :: %s\n", $index + 1, $asset->getName(), $asset->getSize(), $asset->getMimeType());
|
||||
}
|
||||
29
bin/parse-timers.php
Executable file
29
bin/parse-timers.php
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\TimersFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-timers.php <Timers>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$library = TimersFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$timers = $library->getTimers();
|
||||
echo 'Timers (' . count($timers) . ') clock=' . $library->getClockFormat() . ":\n";
|
||||
foreach ($timers as $index => $timer) {
|
||||
$type = $timer->isCountdown() ? 'countdown' : ($timer->isCountdownToTime() ? 'countdown_to_time' : ($timer->isElapsedTime() ? 'elapsed_time' : 'unknown'));
|
||||
$duration = $timer->getDurationSeconds();
|
||||
$durationPart = $duration === null ? '' : sprintf(' :: %ds', $duration);
|
||||
echo sprintf(" [%d] %s :: %s :: %s%s\n", $index + 1, $timer->getName() === '' ? '(unnamed)' : $timer->getName(), $timer->getUuid(), $type, $durationPart);
|
||||
}
|
||||
25
bin/parse-workspace.php
Normal file
25
bin/parse-workspace.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\WorkspaceFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-workspace.php <Workspace>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$library = WorkspaceFileReader::read($argv[1]);
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo 'Screens (' . $library->count() . "):\n";
|
||||
foreach ($library->getScreens() as $index => $screen) {
|
||||
echo sprintf(" [%d] %s :: %s :: type %d\n", $index + 1, $screen->getName() ?: '(unnamed)', $screen->getUuid(), $screen->getScreenType());
|
||||
}
|
||||
108
doc/INDEX.md
108
doc/INDEX.md
|
|
@ -9,8 +9,21 @@
|
|||
| Parse/modify `.pro` song files | [api/song.md](api/song.md) |
|
||||
| Parse/modify `.proplaylist` files | [api/playlist.md](api/playlist.md) |
|
||||
| Parse/modify `.probundle` files | [api/bundle.md](api/bundle.md) |
|
||||
| Read the global `Macros` file | [api/macros.md](api/macros.md) |
|
||||
| Read the global `Labels` file | [api/labels.md](api/labels.md) |
|
||||
| Read/write the global `Macros` file | [api/macros.md](api/macros.md) |
|
||||
| Read/write the global `Labels` file | [api/labels.md](api/labels.md) |
|
||||
| Read/write the global `Groups` file | [api/groups.md](api/groups.md) |
|
||||
| Read/write the global `ClearGroups` file | [api/clear-groups.md](api/clear-groups.md) |
|
||||
| Read/write the global `CCLI` file | [api/ccli.md](api/ccli.md) |
|
||||
| Read/write the global `Messages` file | [api/messages.md](api/messages.md) |
|
||||
| Read/write the global `Timers` file | [api/timers.md](api/timers.md) |
|
||||
| Read/write the global `Stage` file | [api/stage.md](api/stage.md) |
|
||||
| Read/write the global `Workspace` file | [api/workspace.md](api/workspace.md) |
|
||||
| Read/write the global `Props` file | [api/props.md](api/props.md) |
|
||||
| Read/write the global `TestPatterns` file | [api/test-patterns.md](api/test-patterns.md) |
|
||||
| Read/write the global `Calendar` file | [api/calendar.md](api/calendar.md) |
|
||||
| Read/write the global `KeyMappings` file | [api/key-mappings.md](api/key-mappings.md) |
|
||||
| Read/write the global `CommunicationDevices` JSON file | [api/communication-devices.md](api/communication-devices.md) |
|
||||
| Read/write theme folders (Theme + Assets/) | [api/theme.md](api/theme.md) |
|
||||
| Understand `.pro` binary format | [formats/pp_song_spec.md](formats/pp_song_spec.md) |
|
||||
| Understand `.proplaylist` format | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) |
|
||||
| Understand `.probundle` format | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
|
||||
|
|
@ -27,11 +40,27 @@
|
|||
- [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) — ProPresenter 7 `.probundle` file format (ZIP container, media assets)
|
||||
|
||||
### PHP API Documentation
|
||||
#### Document containers
|
||||
- [api/song.md](api/song.md) — Song parser API (read, modify, generate `.pro` files)
|
||||
- [api/playlist.md](api/playlist.md) — Playlist parser API (read, modify, generate `.proplaylist` files)
|
||||
- [api/bundle.md](api/bundle.md) — Bundle parser API (read, write `.probundle` files with media)
|
||||
- [api/macros.md](api/macros.md) — Macros library API (read the global `Macros` file)
|
||||
- [api/labels.md](api/labels.md) — Labels library API (read the global `Labels` file)
|
||||
- [api/theme.md](api/theme.md) — Theme bundle API (folder with `Theme` proto + `Assets/`)
|
||||
|
||||
#### Global library files (read + write)
|
||||
- [api/macros.md](api/macros.md) — `Macros` library (macros + collections, with editable accessors)
|
||||
- [api/labels.md](api/labels.md) — `Labels` library (named slide labels with optional UI colors)
|
||||
- [api/groups.md](api/groups.md) — `Groups` library (named library groups with UUID, color, hot keys)
|
||||
- [api/clear-groups.md](api/clear-groups.md) — `ClearGroups` library (clear-action groups)
|
||||
- [api/ccli.md](api/ccli.md) — `CCLI` settings (license, display behaviour, copyright template)
|
||||
- [api/messages.md](api/messages.md) — `Messages` library (lower-third / overlay messages)
|
||||
- [api/timers.md](api/timers.md) — `Timers` library (timer definitions + clock format)
|
||||
- [api/stage.md](api/stage.md) — `Stage` document (stage display layouts)
|
||||
- [api/workspace.md](api/workspace.md) — `Workspace` document (screens, looks, masks, audio/video inputs)
|
||||
- [api/props.md](api/props.md) — `Props` document (prop cues + transition)
|
||||
- [api/test-patterns.md](api/test-patterns.md) — `TestPatterns` document (currently selected pattern + saved overrides)
|
||||
- [api/calendar.md](api/calendar.md) — `Calendar` document (scheduled events firing macros)
|
||||
- [api/key-mappings.md](api/key-mappings.md) — `KeyMappings` document (custom hot-key bindings)
|
||||
- [api/communication-devices.md](api/communication-devices.md) — `CommunicationDevices` JSON list (MIDI / serial / OSC bindings)
|
||||
|
||||
### Internal Reference
|
||||
- [internal/learnings.md](internal/learnings.md) — Development learnings and conventions discovered
|
||||
|
|
@ -59,8 +88,21 @@ doc/
|
|||
│ ├── song.md
|
||||
│ ├── playlist.md
|
||||
│ ├── bundle.md
|
||||
│ ├── theme.md
|
||||
│ ├── macros.md
|
||||
│ └── labels.md
|
||||
│ ├── labels.md
|
||||
│ ├── groups.md
|
||||
│ ├── clear-groups.md
|
||||
│ ├── ccli.md
|
||||
│ ├── messages.md
|
||||
│ ├── timers.md
|
||||
│ ├── stage.md
|
||||
│ ├── workspace.md
|
||||
│ ├── props.md
|
||||
│ ├── test-patterns.md
|
||||
│ ├── calendar.md
|
||||
│ ├── key-mappings.md
|
||||
│ └── communication-devices.md
|
||||
└── internal/ ← Development notes (optional context)
|
||||
├── learnings.md
|
||||
├── decisions.md
|
||||
|
|
@ -86,6 +128,17 @@ Load: doc/api/playlist.md
|
|||
Load: doc/api/bundle.md
|
||||
```
|
||||
|
||||
### Task: "Edit a global library file (Macros, Labels, Groups, etc.)"
|
||||
```
|
||||
Load: doc/api/<library>.md
|
||||
```
|
||||
|
||||
### Task: "Round-trip a Theme folder with assets"
|
||||
```
|
||||
Load: doc/api/theme.md
|
||||
Load: doc/api/bundle.md (for the bundle pattern reference)
|
||||
```
|
||||
|
||||
### Task: "Debug protobuf parsing issues"
|
||||
```
|
||||
Load: doc/formats/pp_song_spec.md (sections 2-5)
|
||||
|
|
@ -113,6 +166,8 @@ This project provides PHP tools to parse, modify, and generate ProPresenter 7 fi
|
|||
- **Songs** (`.pro`) — Presentation files containing lyrics with groups, slides, arrangements, and translations
|
||||
- **Playlists** (`.proplaylist`) — ZIP archives containing playlist metadata and embedded song files
|
||||
- **Bundles** (`.probundle`) — ZIP archives containing a single presentation with embedded media assets
|
||||
- **Global library files** — `Macros`, `Labels`, `Groups`, `ClearGroups`, `CCLI`, `Messages`, `Timers`, `Stage`, `Workspace`, `Props`, `TestPatterns`, `Calendar`, `KeyMappings`, `CommunicationDevices` (JSON)
|
||||
- **Theme folders** — directory with a `Theme` protobuf file plus an `Assets/` subdirectory of media
|
||||
|
||||
### Key Components
|
||||
|
||||
|
|
@ -129,23 +184,46 @@ This project provides PHP tools to parse, modify, and generate ProPresenter 7 fi
|
|||
| `src/PresentationBundle.php` | Bundle wrapper (read/write `.probundle` files) |
|
||||
| `src/ProBundleReader.php` | Read `.probundle` files |
|
||||
| `src/ProBundleWriter.php` | Write `.probundle` files |
|
||||
| `src/MacroLibrary.php` | Macros library wrapper (read global `Macros` file) |
|
||||
| `src/MacrosFileReader.php` | Read global `Macros` file |
|
||||
| `src/LabelLibrary.php` | Labels library wrapper (read global `Labels` file) |
|
||||
| `src/LabelsFileReader.php` | Read global `Labels` file |
|
||||
| `src/ThemeBundle.php` | Theme folder wrapper (Template.Document + Assets/) |
|
||||
| `src/ThemeFileReader.php` / `ThemeFileWriter.php` | Read/write theme folders |
|
||||
| `src/MacroLibrary.php` / `MacrosFileReader.php` / `MacrosFileWriter.php` | Macros file IO |
|
||||
| `src/LabelLibrary.php` / `LabelsFileReader.php` / `LabelsFileWriter.php` | Labels file IO |
|
||||
| `src/GroupLibrary.php` / `GroupsFileReader.php` / `GroupsFileWriter.php` | Groups file IO |
|
||||
| `src/ClearGroupsLibrary.php` / `ClearGroupsFileReader.php` / `ClearGroupsFileWriter.php` | ClearGroups file IO |
|
||||
| `src/CCLILibrary.php` / `CCLIFileReader.php` / `CCLIFileWriter.php` | CCLI file IO |
|
||||
| `src/MessageLibrary.php` / `MessagesFileReader.php` / `MessagesFileWriter.php` | Messages file IO |
|
||||
| `src/TimersLibrary.php` / `TimersFileReader.php` / `TimersFileWriter.php` | Timers file IO |
|
||||
| `src/StageLibrary.php` / `StageFileReader.php` / `StageFileWriter.php` | Stage file IO |
|
||||
| `src/WorkspaceLibrary.php` / `WorkspaceFileReader.php` / `WorkspaceFileWriter.php` | Workspace file IO |
|
||||
| `src/PropLibrary.php` / `PropsFileReader.php` / `PropsFileWriter.php` | Props file IO |
|
||||
| `src/TestPatternsLibrary.php` / `TestPatternsFileReader.php` / `TestPatternsFileWriter.php` | TestPatterns file IO |
|
||||
| `src/CalendarLibrary.php` / `CalendarFileReader.php` / `CalendarFileWriter.php` | Calendar file IO |
|
||||
| `src/KeyMappingsLibrary.php` / `KeyMappingsFileReader.php` / `KeyMappingsFileWriter.php` | KeyMappings file IO |
|
||||
| `src/CommunicationDevicesLibrary.php` / `CommunicationDevicesFileReader.php` / `CommunicationDevicesFileWriter.php` | CommunicationDevices JSON file IO |
|
||||
|
||||
### CLI Tools
|
||||
|
||||
```bash
|
||||
# Parse and display song structure
|
||||
# Songs / playlists / bundles
|
||||
php bin/parse-song.php path/to/song.pro
|
||||
|
||||
# Parse and display playlist structure
|
||||
php bin/parse-playlist.php path/to/playlist.proplaylist
|
||||
|
||||
# Parse and display the global Macros file
|
||||
# Global library files
|
||||
php bin/parse-macros.php path/to/Macros
|
||||
|
||||
# Parse and display the global Labels file
|
||||
php bin/parse-labels.php path/to/Labels
|
||||
php bin/parse-groups.php path/to/Groups
|
||||
php bin/parse-clear-groups.php path/to/ClearGroups
|
||||
php bin/parse-ccli.php path/to/CCLI
|
||||
php bin/parse-messages.php path/to/Messages
|
||||
php bin/parse-timers.php path/to/Timers
|
||||
php bin/parse-stage.php path/to/Stage
|
||||
php bin/parse-workspace.php path/to/Workspace
|
||||
php bin/parse-props.php path/to/Props
|
||||
php bin/parse-test-patterns.php path/to/TestPatterns
|
||||
php bin/parse-calendar.php path/to/Calendar
|
||||
php bin/parse-key-mappings.php path/to/KeyMappings
|
||||
php bin/parse-communication-devices.php path/to/CommunicationDevices
|
||||
|
||||
# Theme folder
|
||||
php bin/parse-theme.php path/to/ThemeFolder
|
||||
```
|
||||
|
|
|
|||
115
doc/api/calendar.md
Normal file
115
doc/api/calendar.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# Calendar Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter `Calendar` file
|
||||
> (raw protobuf, no extension) and preserving scheduled macro events.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\CalendarFileReader;
|
||||
|
||||
$library = CalendarFileReader::read('/path/to/Calendar');
|
||||
|
||||
foreach ($library->getEvents() as $event) {
|
||||
$event->getName();
|
||||
$event->getStartTimeSeconds();
|
||||
$event->getActionData(); // raw bytes
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `Calendar` file is the protobuf-serialised
|
||||
[`CalendarDocument`](../../proto/calendar.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `events` | repeated `CalendarDocument.Event` | Scheduled events |
|
||||
| `mode` | `uint32` | Opaque document mode/source flag |
|
||||
|
||||
Each event includes UUID, name, start/end timestamps, opaque `flags`, and two
|
||||
raw bytes fields: `action_data` (field 8) and `macro_data` (field 9). Those
|
||||
bytes are intentionally not decoded by this wrapper.
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
$library = CalendarFileReader::read('/Users/me/.../Calendar');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## CalendarLibrary
|
||||
|
||||
```php
|
||||
$library->getEvents(); // CalendarEvent[]
|
||||
$library->count(); // int
|
||||
$library->getEventByUuid($uuid); // ?CalendarEvent (case-insensitive)
|
||||
$library->getEventByName($name); // ?CalendarEvent
|
||||
$library->addEvent($name, $uuid); // CalendarEvent
|
||||
$library->removeEvent($uuid); // bool
|
||||
$library->getMode(); // int
|
||||
$library->setMode(1); // void
|
||||
$library->getDocument(); // \Rv\Data\CalendarDocument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CalendarEvent
|
||||
|
||||
```php
|
||||
$event->getUuid();
|
||||
$event->setUuid($uuid);
|
||||
$event->getName();
|
||||
$event->setName($name);
|
||||
$event->getStartTime();
|
||||
$event->setStartTime($timestamp);
|
||||
$event->getStartTimeSeconds();
|
||||
$event->setStartTimeSeconds($seconds);
|
||||
$event->getEndTime();
|
||||
$event->getEndTimeSeconds();
|
||||
$event->getFlags();
|
||||
$event->setFlags($bytes);
|
||||
$event->getActionData(); // raw protobuf bytes
|
||||
$event->setActionData($bytes);
|
||||
$event->getMacroData(); // raw protobuf bytes
|
||||
$event->setMacroData($bytes);
|
||||
$event->getProto();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-calendar.php /path/to/Calendar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/CalendarLibrary.php` | Document wrapper with UUID / name indexes |
|
||||
| `src/CalendarEvent.php` | Single event wrapper |
|
||||
| `src/CalendarFileReader.php` | Reads the `Calendar` file |
|
||||
| `src/CalendarFileWriter.php` | Writes the `Calendar` file |
|
||||
| `bin/parse-calendar.php` | CLI summary tool |
|
||||
| `proto/calendar.proto` | Protobuf schema |
|
||||
| `generated/Rv/Data/CalendarDocument.php` | Generated document class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
`action_data` and `macro_data` are raw protobuf byte strings. They are exposed
|
||||
directly for byte-preserving edits and round trips; semantic decoding belongs in
|
||||
a future schema-specific module.
|
||||
104
doc/api/ccli.md
Normal file
104
doc/api/ccli.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# CCLI Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter `CCLI` file (raw
|
||||
> protobuf, no extension) that controls copyright display settings.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\CCLIFileReader;
|
||||
use ProPresenter\Parser\CCLIFileWriter;
|
||||
|
||||
$library = CCLIFileReader::read('/path/to/CCLI');
|
||||
|
||||
$library->isCCLIDisplayEnabled(); // bool
|
||||
$library->getCCLILicense(); // string
|
||||
$library->getDisplayType(); // int enum value
|
||||
$library->getTemplate(); // ?\Rv\Data\Template\Slide
|
||||
|
||||
$library->setCCLILicense('1234567');
|
||||
CCLIFileWriter::write($library, '/path/to/CCLI');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `CCLI` file is the protobuf-serialised
|
||||
[`CCLIDocument`](../../proto/ccli.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `application_info` | `ApplicationInfo` | ProPresenter writer metadata |
|
||||
| `enable_ccli_display` | bool | Whether copyright info is shown |
|
||||
| `ccli_license` | string | CCLI license number |
|
||||
| `display_type` | `CCLIDocument.DisplayType` | First, last, first+last, or all slides |
|
||||
| `template` | `Template.Slide` | Text/template styling for display |
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\CCLIFileReader;
|
||||
|
||||
$library = CCLIFileReader::read('/Users/me/.../CCLI');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## CCLILibrary
|
||||
|
||||
Top-level wrapper around `Rv\Data\CCLIDocument`. This is a single-document
|
||||
configuration file; `count()` returns `1` when read successfully.
|
||||
|
||||
```php
|
||||
$library->count();
|
||||
$library->isCCLIDisplayEnabled();
|
||||
$library->setCCLIDisplayEnabled(true);
|
||||
$library->getCCLILicense();
|
||||
$library->setCCLILicense('1234567');
|
||||
$library->getDisplayType();
|
||||
$library->setDisplayType(3);
|
||||
$library->getTemplate();
|
||||
$library->setTemplate($slideOrNull);
|
||||
$library->getDocument(); // \Rv\Data\CCLIDocument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-ccli.php /path/to/CCLI
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
CCLI (1):
|
||||
[1] enabled=yes :: license=(empty) :: display_type=0 :: template=yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/CCLILibrary.php` | Document-level wrapper |
|
||||
| `src/CCLIFileReader.php` | Reads the `CCLI` file |
|
||||
| `src/CCLIFileWriter.php` | Writes the `CCLI` file |
|
||||
| `bin/parse-ccli.php` | CLI tool |
|
||||
| `proto/ccli.proto` | Protobuf schema |
|
||||
| `generated/Rv/Data/CCLIDocument.php` | Generated message class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
The wrapper preserves template data and application metadata by mutating the
|
||||
generated protobuf in place. It does not inspect or render the slide template.
|
||||
135
doc/api/clear-groups.md
Normal file
135
doc/api/clear-groups.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# ClearGroups Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter `ClearGroups` file
|
||||
> (raw protobuf, no extension) and exposing each clear group definition.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\ClearGroupsFileReader;
|
||||
use ProPresenter\Parser\ClearGroupsFileWriter;
|
||||
|
||||
$library = ClearGroupsFileReader::read('/path/to/ClearGroups');
|
||||
|
||||
foreach ($library->getGroups() as $group) {
|
||||
$group->getName(); // "Alles ausblenden"
|
||||
$group->getUuid(); // "A91C6AFE-..."
|
||||
$group->getImageType(); // 11
|
||||
$group->getColorHex(); // "#FFFFFF" | null
|
||||
}
|
||||
|
||||
ClearGroupsFileWriter::write($library, '/path/to/ClearGroups');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `ClearGroups` file is the protobuf-serialised
|
||||
[`ClearGroupsDocument`](../../proto/clearGroups.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `application_info` | `ApplicationInfo` | ProPresenter writer metadata |
|
||||
| `groups` | repeated `ClearGroupsDocument.ClearGroup` | Clear button definitions |
|
||||
|
||||
Each `ClearGroup` carries:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | `UUID` | Stable identifier |
|
||||
| `name` | string | Display name |
|
||||
| `layer_targets` | repeated `Action.ClearType` | Layers cleared by the button |
|
||||
| `is_hidden_in_preview` | bool | Whether preview UI hides the button |
|
||||
| `image_data` | bytes | Custom icon payload |
|
||||
| `image_type` | enum | Built-in icon identifier |
|
||||
| `is_icon_tinted` | bool | Whether `icon_tint_color` applies |
|
||||
| `icon_tint_color` | `Color` | RGBA float channels in 0..1 |
|
||||
| `timeline_targets` | repeated `Action.ContentDestination` | Timeline destinations |
|
||||
| `clear_presentation_next_slide` | bool | Also clear the queued next slide |
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\ClearGroupsFileReader;
|
||||
|
||||
$library = ClearGroupsFileReader::read('/Users/me/.../ClearGroups');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## ClearGroupsLibrary
|
||||
|
||||
Top-level wrapper around `Rv\Data\ClearGroupsDocument`. Indexes groups by UUID
|
||||
(case-insensitive) and name.
|
||||
|
||||
```php
|
||||
$library->getGroups(); // ClearGroupDefinition[]
|
||||
$library->count(); // int
|
||||
$library->getClearGroupByUuid('A91C...'); // ?ClearGroupDefinition
|
||||
$library->getClearGroupByName('Alles ...'); // ?ClearGroupDefinition
|
||||
$library->addClearGroup('Name', 'UUID'); // ClearGroupDefinition
|
||||
$library->removeClearGroup('UUID'); // bool
|
||||
$library->getDocument(); // \Rv\Data\ClearGroupsDocument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ClearGroupDefinition
|
||||
|
||||
```php
|
||||
$group->getName();
|
||||
$group->setName('New Name');
|
||||
$group->getUuid();
|
||||
$group->setUuid('...');
|
||||
$group->getLayerTargets();
|
||||
$group->getImageType();
|
||||
$group->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
|
||||
$group->getColorHex(); // "#RRGGBB" uppercase, alpha dropped, or null
|
||||
$group->getProto(); // \Rv\Data\ClearGroupsDocument\ClearGroup
|
||||
```
|
||||
|
||||
Color channels are floats in 0..1 as ProPresenter stores them. `getColorHex()`
|
||||
clamps and rounds each channel to 8 bits before formatting.
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-clear-groups.php /path/to/ClearGroups
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
ClearGroups (1):
|
||||
[1] Alles ausblenden :: A91C6AFE-098F-4559-B2CF-D8373C589589 :: image_type=11 :: #FFFFFF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/ClearGroupsLibrary.php` | Document-level wrapper with UUID/name lookups |
|
||||
| `src/ClearGroupDefinition.php` | Single clear group wrapper |
|
||||
| `src/ClearGroupsFileReader.php` | Reads the `ClearGroups` file |
|
||||
| `src/ClearGroupsFileWriter.php` | Writes the `ClearGroups` file |
|
||||
| `bin/parse-clear-groups.php` | CLI tool |
|
||||
| `proto/clearGroups.proto` | Protobuf schema |
|
||||
| `generated/Rv/Data/ClearGroupsDocument.php` | Generated message class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
The wrapper preserves unknown / uninterpreted protobuf data by mutating the
|
||||
generated message in place and serialising it back. It does not execute clear
|
||||
actions or modify slide content.
|
||||
96
doc/api/communication-devices.md
Normal file
96
doc/api/communication-devices.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# CommunicationDevices Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter
|
||||
> `CommunicationDevices` file. Unlike most config files in this project, this
|
||||
> file is JSON, not protobuf.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\CommunicationDevice;
|
||||
use ProPresenter\Parser\CommunicationDevicesFileReader;
|
||||
use ProPresenter\Parser\CommunicationDevicesFileWriter;
|
||||
|
||||
$library = CommunicationDevicesFileReader::read('/path/to/CommunicationDevices');
|
||||
$library->addDevice((new CommunicationDevice())->setId('device-1')->setName('Router'));
|
||||
CommunicationDevicesFileWriter::write($library, '/path/to/CommunicationDevices');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
`CommunicationDevices` is a JSON array. The reference sample is `[]`, so the
|
||||
wrapper preserves arbitrary object fields while exposing forward-looking common
|
||||
fields: `id`, `name`, `type`, and `address`.
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
$library = CommunicationDevicesFileReader::read('/Users/me/.../CommunicationDevices');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
unreadable files or invalid JSON.
|
||||
|
||||
---
|
||||
|
||||
## CommunicationDevicesLibrary
|
||||
|
||||
```php
|
||||
CommunicationDevicesLibrary::fromJson($json); // CommunicationDevicesLibrary
|
||||
$library->toJson(); // string
|
||||
$library->getDocument(); // raw decoded array
|
||||
$library->getDevices(); // CommunicationDevice[]
|
||||
$library->addDevice($device); // CommunicationDevice
|
||||
$library->removeDevice($id); // bool
|
||||
$library->count(); // int
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CommunicationDevice
|
||||
|
||||
```php
|
||||
$device->getId();
|
||||
$device->setId($id);
|
||||
$device->getName();
|
||||
$device->setName($name);
|
||||
$device->getType();
|
||||
$device->setType($type);
|
||||
$device->getAddress();
|
||||
$device->setAddress($address);
|
||||
$device->toArray(); // full decoded JSON object
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-communication-devices.php /path/to/CommunicationDevices
|
||||
```
|
||||
|
||||
Empty files print a useful `(none configured)` summary.
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/CommunicationDevicesLibrary.php` | JSON-array wrapper |
|
||||
| `src/CommunicationDevice.php` | Single JSON device value object |
|
||||
| `src/CommunicationDevicesFileReader.php` | Reads and validates JSON |
|
||||
| `src/CommunicationDevicesFileWriter.php` | Writes compact JSON |
|
||||
| `bin/parse-communication-devices.php` | CLI summary tool |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
Because only an empty sample is available, unknown JSON fields are preserved in
|
||||
each device's backing array. The writer uses compact JSON with unescaped
|
||||
slashes / Unicode for stable semantic round trips.
|
||||
172
doc/api/groups.md
Normal file
172
doc/api/groups.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# 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.
|
||||
130
doc/api/key-mappings.md
Normal file
130
doc/api/key-mappings.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# KeyMappings Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter `KeyMappings` file
|
||||
> (raw protobuf, no extension) and exposing configured hot-key mappings.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\KeyMappingsFileReader;
|
||||
use ProPresenter\Parser\KeyMappingsFileWriter;
|
||||
|
||||
$library = KeyMappingsFileReader::read('/path/to/KeyMappings');
|
||||
|
||||
foreach ($library->getMappings() as $mapping) {
|
||||
$mapping->getName(); // string
|
||||
$mapping->getUuid(); // string
|
||||
$mapping->getHotKey(); // ?\Rv\Data\HotKey
|
||||
$mapping->getTarget(); // raw bytes
|
||||
}
|
||||
|
||||
KeyMappingsFileWriter::write($library, '/path/to/KeyMappings');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `KeyMappings` file is the protobuf-serialised
|
||||
[`KeyMappingsDocument`](../../proto/keyMappings.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `application_info` | `ApplicationInfo` | ProPresenter writer metadata |
|
||||
| `mappings` | repeated `KeyMappingsDocument.Mapping` | Configured key bindings |
|
||||
|
||||
Each `Mapping` carries:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | `UUID` | Optional stable mapping identifier |
|
||||
| `hot_key` | `HotKey` | Key combo that fires the action |
|
||||
| `target` | bytes | Raw target reference |
|
||||
| `name` | string | Optional display name |
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\KeyMappingsFileReader;
|
||||
|
||||
$library = KeyMappingsFileReader::read('/Users/me/.../KeyMappings');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## KeyMappingsLibrary
|
||||
|
||||
Top-level wrapper around `Rv\Data\KeyMappingsDocument`. Indexes mappings by UUID
|
||||
(case-insensitive) and name.
|
||||
|
||||
```php
|
||||
$library->getMappings();
|
||||
$library->count();
|
||||
$library->getMappingByUuid('...');
|
||||
$library->getMappingByName('Macro trigger');
|
||||
$library->addMapping('Macro trigger', 'UUID', $targetBytes);
|
||||
$library->removeMapping('UUID');
|
||||
$library->getApplicationInfo();
|
||||
$library->setApplicationInfo($infoOrNull);
|
||||
$library->getDocument(); // \Rv\Data\KeyMappingsDocument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## KeyMapping
|
||||
|
||||
```php
|
||||
$mapping->getName();
|
||||
$mapping->setName('New Name');
|
||||
$mapping->getUuid();
|
||||
$mapping->setUuid('...');
|
||||
$mapping->getHotKey();
|
||||
$mapping->setHotKey($hotKeyOrNull);
|
||||
$mapping->getTarget();
|
||||
$mapping->setTarget($bytes);
|
||||
$mapping->getProto(); // \Rv\Data\KeyMappingsDocument\Mapping
|
||||
```
|
||||
|
||||
Targets are raw bytes because ProPresenter may encode several internal target
|
||||
types here. Keeping bytes opaque preserves round-trip safety.
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-key-mappings.php /path/to/KeyMappings
|
||||
```
|
||||
|
||||
Output for the reference sample:
|
||||
|
||||
```
|
||||
KeyMappings (0):
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/KeyMappingsLibrary.php` | Document-level wrapper with UUID/name lookups |
|
||||
| `src/KeyMapping.php` | Single mapping wrapper |
|
||||
| `src/KeyMappingsFileReader.php` | Reads the `KeyMappings` file |
|
||||
| `src/KeyMappingsFileWriter.php` | Writes the `KeyMappings` file |
|
||||
| `bin/parse-key-mappings.php` | CLI tool |
|
||||
| `proto/keyMappings.proto` | Protobuf schema |
|
||||
| `generated/Rv/Data/KeyMappingsDocument.php` | Generated message class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
The reference sample contains only `ApplicationInfo` and no mappings. The API
|
||||
still supports mapping additions/removals so configured user files can be edited
|
||||
and round-tripped safely.
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
```php
|
||||
use ProPresenter\Parser\LabelsFileReader;
|
||||
use ProPresenter\Parser\LabelsFileWriter;
|
||||
|
||||
$library = LabelsFileReader::read('/path/to/Labels');
|
||||
|
||||
|
|
@ -16,6 +17,14 @@ foreach ($library->getLabels() as $label) {
|
|||
$label->getColor(); // ['r'=>0.0,'g'=>0.408,'b'=>0.702,'a'=>1.0] | null
|
||||
$label->getColorHex(); // "#0068B3" | null
|
||||
}
|
||||
|
||||
// Modify and persist
|
||||
$library->addLabel('NewLabel', ['r' => 1.0, 'g' => 0.5, 'b' => 0.0]);
|
||||
$beamer = $library->getLabelByName('KeyVisual Beamer');
|
||||
$beamer?->setColorHex('#FF8800');
|
||||
$library->removeLabel('Wiederholen');
|
||||
|
||||
LabelsFileWriter::write($library, '/path/to/Labels');
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -54,6 +63,19 @@ empty / unreadable files.
|
|||
|
||||
---
|
||||
|
||||
## Writing
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\LabelsFileWriter;
|
||||
|
||||
LabelsFileWriter::write($library, '/Users/me/.../Labels');
|
||||
```
|
||||
|
||||
Serialises the underlying `ProLabelsDocument` to bytes. The unmodified
|
||||
reference sample round-trips byte-for-byte.
|
||||
|
||||
---
|
||||
|
||||
## LabelLibrary
|
||||
|
||||
Top-level wrapper around `Rv\Data\ProLabelsDocument`. Indexes labels by name
|
||||
|
|
@ -64,7 +86,10 @@ $library->getLabels(); // Label[]
|
|||
$library->count(); // int
|
||||
$library->getLabelByName('Szene 1'); // ?Label (case-sensitive)
|
||||
$library->findLabelByName('szene 1'); // ?Label (case-insensitive)
|
||||
$library->getDocument(); // \Rv\Data\ProLabelsDocument
|
||||
|
||||
$library->addLabel('NewLabel', ['r'=>1, 'g'=>0, 'b'=>0]); // ?Label
|
||||
$library->removeLabel('OldLabel'); // bool
|
||||
$library->getDocument(); // \Rv\Data\ProLabelsDocument
|
||||
```
|
||||
|
||||
If the same name appears more than once in the source document the first
|
||||
|
|
@ -76,11 +101,15 @@ occurrence wins for both lookup helpers; every entry is preserved in
|
|||
## Label
|
||||
|
||||
```php
|
||||
$label->getName(); // "KeyVisual Beamer" (proto field is `text`)
|
||||
$label->hasColor(); // bool — was a Color message present?
|
||||
$label->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
|
||||
$label->getColorHex(); // "#RRGGBB" uppercase, alpha dropped, or null
|
||||
$label->getProto(); // \Rv\Data\Action\Label (raw protobuf)
|
||||
$label->getName(); // "KeyVisual Beamer" (proto field is `text`)
|
||||
$label->setName('Renamed'); // self
|
||||
$label->hasColor(); // bool — was a Color message present?
|
||||
$label->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
|
||||
$label->getColorHex(); // "#RRGGBB" uppercase, alpha dropped, or null
|
||||
$label->setColor(['r'=>1, 'g'=>0, 'b'=>0]); // self
|
||||
$label->setColor(null); // clears the color (UI falls back to default)
|
||||
$label->setColorHex('#FF8800'); // accepts #RRGGBB or #RRGGBBAA
|
||||
$label->getProto(); // \Rv\Data\Action\Label (raw protobuf)
|
||||
```
|
||||
|
||||
Color channels are floats in 0..1 as ProPresenter stores them. `getColorHex()`
|
||||
|
|
@ -118,9 +147,10 @@ Labels (15):
|
|||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/LabelLibrary.php` | Document-level wrapper with name lookups |
|
||||
| `src/Label.php` | Single label wrapper (name, color, hex) |
|
||||
| `src/LabelLibrary.php` | Document-level wrapper with name lookups + add / remove helpers |
|
||||
| `src/Label.php` | Single label wrapper (name, color, hex) with setters |
|
||||
| `src/LabelsFileReader.php` | Reads the `Labels` file |
|
||||
| `src/LabelsFileWriter.php` | Writes the `Labels` file |
|
||||
| `bin/parse-labels.php` | CLI tool |
|
||||
| `proto/labels.proto` | Protobuf schema (just imports `Action.Label`) |
|
||||
| `proto/action.proto` | Defines the inner `Action.Label` message |
|
||||
|
|
@ -131,7 +161,6 @@ Labels (15):
|
|||
|
||||
## Scope Notes
|
||||
|
||||
This module is read-only by design. Writing the `Labels` file back, editing
|
||||
slide-side label references on `.pro` files, or syncing labels across devices
|
||||
are not implemented here. Add them by mirroring the `ProFileWriter` /
|
||||
`ProFileGenerator` pattern when needed.
|
||||
Editing slide-side label references on `.pro` files (cross-document fan-out)
|
||||
and syncing labels across devices are out of scope; this module only covers
|
||||
the global `Labels` document.
|
||||
|
|
|
|||
|
|
@ -8,18 +8,27 @@
|
|||
|
||||
```php
|
||||
use ProPresenter\Parser\MacrosFileReader;
|
||||
use ProPresenter\Parser\MacrosFileWriter;
|
||||
|
||||
$library = MacrosFileReader::read('/path/to/Macros');
|
||||
|
||||
foreach ($library->getMacros() as $macro) {
|
||||
$macro->getName(); // "Gottesdienst START"
|
||||
$macro->getUuid(); // "FA0602E4-EDA2-4457-BB62-68AA17184217"
|
||||
$macro->getName(); // "Gottesdienst START"
|
||||
$macro->getUuid(); // "FA0602E4-EDA2-4457-BB62-68AA17184217"
|
||||
$macro->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
|
||||
$macro->getImageType(); // int — see ImageType enum
|
||||
$macro->getImageData(); // bytes — custom icon (empty for built-ins)
|
||||
|
||||
foreach ($library->getCollectionsForMacro($macro) as $collection) {
|
||||
$collection->getName(); // "Ablauf"
|
||||
$collection->getUuid(); // "8D02FC57-83F8-4042-9B90-81C229728426"
|
||||
$collection->getName();
|
||||
$collection->getUuid();
|
||||
}
|
||||
}
|
||||
|
||||
// Modify and persist
|
||||
$library->addMacro('NewMacro', '...uuid...');
|
||||
$library->getMacroByName('NewMacro')?->setColor(['r'=>0.5, 'g'=>0, 'b'=>1]);
|
||||
MacrosFileWriter::write($library, '/path/to/Macros');
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -53,6 +62,20 @@ empty / unreadable files.
|
|||
|
||||
---
|
||||
|
||||
## Writing
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\MacrosFileWriter;
|
||||
|
||||
MacrosFileWriter::write($library, '/Users/me/.../Macros');
|
||||
```
|
||||
|
||||
Serialises the underlying `MacrosDocument` to bytes. Round-trip preserves the
|
||||
overall byte length; field ordering can vary slightly because the protobuf
|
||||
PHP runtime is not guaranteed to be canonical.
|
||||
|
||||
---
|
||||
|
||||
## MacroLibrary
|
||||
|
||||
Top-level wrapper around `Rv\Data\MacrosDocument`. Indexes macros and
|
||||
|
|
@ -71,6 +94,12 @@ $library->getCollectionByName('Ablauf'); // ?MacroCollection
|
|||
$library->getMacrosForCollection($collection); // Macro[] in declared order
|
||||
$library->getCollectionsForMacro($macro); // MacroCollection[] (membership)
|
||||
|
||||
// Mutators
|
||||
$library->addMacro('NewMacro', '...uuid...'); // Macro
|
||||
$library->removeMacro('...uuid...'); // bool
|
||||
$library->addCollection('NewCollection', '...uuid...'); // MacroCollection
|
||||
$library->removeCollection('...uuid...'); // bool
|
||||
|
||||
$library->getDocument(); // \Rv\Data\MacrosDocument (raw protobuf)
|
||||
```
|
||||
|
||||
|
|
@ -80,11 +109,18 @@ $library->getDocument(); // \Rv\Data\MacrosDocument (raw protobuf)
|
|||
|
||||
```php
|
||||
$macro->getUuid(); // "FA0602E4-..."
|
||||
$macro->setUuid('...'); // self
|
||||
$macro->getName(); // "Gottesdienst START"
|
||||
$macro->setName('...'); // self
|
||||
$macro->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
|
||||
$macro->setColor(['r'=>0.5,'g'=>0,'b'=>1]); // self (or null to clear)
|
||||
$macro->getTriggerOnStartup(); // bool
|
||||
$macro->setTriggerOnStartup(true); // self
|
||||
$macro->getActionCount(); // int — number of attached Action entries
|
||||
$macro->getImageType(); // int — see Rv\Data\MacrosDocument\Macro\ImageType
|
||||
$macro->setImageType(...); // self — pass an ImageType enum value
|
||||
$macro->getImageData(); // string — custom icon bytes (empty for built-ins)
|
||||
$macro->setImageData($pngBytes); // self — set a custom icon
|
||||
$macro->getProto(); // \Rv\Data\MacrosDocument\Macro
|
||||
```
|
||||
|
||||
|
|
@ -96,10 +132,14 @@ walk `getActions()` directly when needed.
|
|||
## MacroCollection
|
||||
|
||||
```php
|
||||
$collection->getUuid(); // "8D02FC57-..."
|
||||
$collection->getName(); // "Ablauf"
|
||||
$collection->getMacroUuids(); // string[] — referenced macro UUIDs in order
|
||||
$collection->getProto(); // \Rv\Data\MacrosDocument\MacroCollection
|
||||
$collection->getUuid(); // "8D02FC57-..."
|
||||
$collection->setUuid('...'); // self
|
||||
$collection->getName(); // "Ablauf"
|
||||
$collection->setName('...'); // self
|
||||
$collection->getMacroUuids(); // string[] — referenced macro UUIDs in order
|
||||
$collection->setMacroUuids(['...']); // self — replace all referenced UUIDs
|
||||
$collection->addMacroUuid('...'); // self — append a single reference
|
||||
$collection->getProto(); // \Rv\Data\MacrosDocument\MacroCollection
|
||||
```
|
||||
|
||||
Items use a protobuf `oneof ItemType`; only `macro_id` is currently defined.
|
||||
|
|
@ -132,10 +172,11 @@ Collections (3):
|
|||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/MacroLibrary.php` | Document-level wrapper with lookup helpers |
|
||||
| `src/Macro.php` | Single macro wrapper |
|
||||
| `src/MacroCollection.php` | Collection wrapper |
|
||||
| `src/MacroLibrary.php` | Document-level wrapper with lookup + add / remove helpers |
|
||||
| `src/Macro.php` | Single macro wrapper with setters |
|
||||
| `src/MacroCollection.php` | Collection wrapper with setters |
|
||||
| `src/MacrosFileReader.php` | Reads the `Macros` file |
|
||||
| `src/MacrosFileWriter.php` | Writes the `Macros` file |
|
||||
| `bin/parse-macros.php` | CLI tool |
|
||||
| `proto/macros.proto` | Protobuf schema |
|
||||
| `generated/Rv/Data/MacrosDocument.php` | Generated message classes |
|
||||
|
|
@ -144,8 +185,8 @@ Collections (3):
|
|||
|
||||
## Scope Notes
|
||||
|
||||
This module is read-only by design. Action editing, slide-side macro
|
||||
references on `.pro` files (see `Slide::getMacroUuid()` /
|
||||
`Slide::setMacro()`), and writing the `Macros` file back are not implemented
|
||||
here. Add them by mirroring the `ProFileWriter` / `ProFileGenerator` pattern
|
||||
when needed.
|
||||
Action editing (the inner `repeated Action actions` field on each macro) and
|
||||
slide-side macro references on `.pro` files (see `Slide::getMacroUuid()` /
|
||||
`Slide::setMacro()`) are out of scope. This module covers the global
|
||||
`Macros` document only; reach for `getProto()->getActions()` for raw action
|
||||
inspection.
|
||||
|
|
|
|||
110
doc/api/messages.md
Normal file
110
doc/api/messages.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Messages Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter `Messages` file
|
||||
> (raw protobuf, no extension) and exposing each message definition.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\MessagesFileReader;
|
||||
use ProPresenter\Parser\MessagesFileWriter;
|
||||
|
||||
$library = MessagesFileReader::read('/path/to/Messages');
|
||||
|
||||
foreach ($library->getMessages() as $message) {
|
||||
$message->getTitle();
|
||||
$message->getUuid();
|
||||
$message->getMessageText();
|
||||
}
|
||||
|
||||
$library->addMessage('Lobby Notice', '11111111-1111-1111-1111-111111111111');
|
||||
MessagesFileWriter::write($library, '/path/to/Messages');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `Messages` file is the protobuf-serialised
|
||||
[`MessageDocument`](../../proto/messages.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `application_info` | `ApplicationInfo` | ProPresenter metadata |
|
||||
| `messages` | repeated `Message` | Message definitions in document order |
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
$library = MessagesFileReader::read('/Users/me/.../Messages');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## MessageLibrary
|
||||
|
||||
```php
|
||||
$library->getMessages(); // Message[]
|
||||
$library->count(); // int
|
||||
$library->getMessageByUuid($uuid); // ?Message (case-insensitive)
|
||||
$library->getMessageByName($title); // ?Message (case-sensitive title)
|
||||
$library->addMessage($title, $uuid); // Message
|
||||
$library->removeMessage($uuid); // bool
|
||||
$library->getApplicationInfo(); // ?\Rv\Data\ApplicationInfo
|
||||
$library->getDocument(); // \Rv\Data\MessageDocument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Message
|
||||
|
||||
```php
|
||||
$message->getUuid();
|
||||
$message->setUuid($uuid);
|
||||
$message->getTitle();
|
||||
$message->setTitle($title);
|
||||
$message->getTimeToRemove();
|
||||
$message->setTimeToRemove($seconds);
|
||||
$message->isVisibleOnNetwork();
|
||||
$message->setVisibleOnNetwork(true);
|
||||
$message->getMessageText();
|
||||
$message->setMessageText($text);
|
||||
$message->getClearType();
|
||||
$message->setClearType($enumValue);
|
||||
$message->getTokens(); // raw repeated Token protos
|
||||
$message->getProto(); // \Rv\Data\Message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-messages.php /path/to/Messages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/MessageLibrary.php` | Document wrapper with title / UUID indexes |
|
||||
| `src/Message.php` | Single message wrapper |
|
||||
| `src/MessagesFileReader.php` | Reads the `Messages` file |
|
||||
| `src/MessagesFileWriter.php` | Writes the `Messages` file |
|
||||
| `bin/parse-messages.php` | CLI summary tool |
|
||||
| `generated/Rv/Data/MessageDocument.php` | Generated document class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
Tokens and token values are preserved as raw generated protobuf objects. The
|
||||
wrapper exposes them for advanced callers but does not interpret template
|
||||
rendering semantics.
|
||||
80
doc/api/props.md
Normal file
80
doc/api/props.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Props Library API
|
||||
|
||||
> PHP module for reading, modifying, and writing the global ProPresenter `Props` file.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\PropsFileReader;
|
||||
use ProPresenter\Parser\PropsFileWriter;
|
||||
|
||||
$library = PropsFileReader::read('/path/to/Props');
|
||||
|
||||
foreach ($library->getProps() as $prop) {
|
||||
$prop->getName();
|
||||
$prop->getUuid();
|
||||
$prop->isEnabled();
|
||||
}
|
||||
|
||||
PropsFileWriter::write($library, '/path/to/Props');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `Props` file is `Rv\Data\PropDocument` from `propDocument.proto`.
|
||||
Each prop is stored as a `Rv\Data\Cue` in the document's `cues` field.
|
||||
|
||||
---
|
||||
|
||||
## PropLibrary
|
||||
|
||||
```php
|
||||
$library->getDocument();
|
||||
$library->getProps();
|
||||
$library->getPropByUuid('1FB2...'); // case-insensitive
|
||||
$library->getPropByName('Props #1');
|
||||
$library->addProp($prop);
|
||||
$library->removeProp($uuid);
|
||||
$library->count();
|
||||
$library->getApplicationInfo();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prop
|
||||
|
||||
```php
|
||||
$prop->getName();
|
||||
$prop->setName('Lower Third');
|
||||
$prop->getUuid();
|
||||
$prop->setUuid('...');
|
||||
$prop->isEnabled();
|
||||
$prop->setEnabled(true);
|
||||
$prop->getCompletionTime();
|
||||
$prop->getActions();
|
||||
$prop->getProto();
|
||||
```
|
||||
|
||||
Use `getProto()` for full Cue/action access.
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-props.php /path/to/Props
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/PropsFileReader.php` | Reads the `Props` file |
|
||||
| `src/PropsFileWriter.php` | Writes the `Props` file |
|
||||
| `src/PropLibrary.php` | Document wrapper and indexes |
|
||||
| `src/Prop.php` | Single Cue/prop wrapper |
|
||||
| `bin/parse-props.php` | CLI summary tool |
|
||||
83
doc/api/stage.md
Normal file
83
doc/api/stage.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Stage Library API
|
||||
|
||||
> PHP module for reading, modifying, and writing the global ProPresenter `Stage` file.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\StageFileReader;
|
||||
use ProPresenter\Parser\StageFileWriter;
|
||||
|
||||
$library = StageFileReader::read('/path/to/Stage');
|
||||
|
||||
foreach ($library->getLayouts() as $layout) {
|
||||
$layout->getName();
|
||||
$layout->getUuid();
|
||||
$layout->getSlide(); // ?\Rv\Data\Slide
|
||||
}
|
||||
|
||||
StageFileWriter::write($library, '/path/to/Stage');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `Stage` file is the protobuf-serialised `Rv\Data\Stage\Document` from
|
||||
`stage.proto`.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `application_info` | `ApplicationInfo` | ProPresenter metadata |
|
||||
| `layouts` | repeated `Stage.Layout` | Stage display layouts |
|
||||
|
||||
---
|
||||
|
||||
## StageLibrary
|
||||
|
||||
```php
|
||||
$library->getDocument();
|
||||
$library->getLayouts();
|
||||
$library->getLayoutByUuid('0455...'); // case-insensitive
|
||||
$library->getLayoutByName('Default StageDisplay');
|
||||
$library->addLayout($layout);
|
||||
$library->removeLayout($uuid);
|
||||
$library->count();
|
||||
$library->getApplicationInfo();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## StageLayout
|
||||
|
||||
```php
|
||||
$layout->getName();
|
||||
$layout->setName('New name');
|
||||
$layout->getUuid();
|
||||
$layout->setUuid('...');
|
||||
$layout->getSlide();
|
||||
$layout->getProto();
|
||||
```
|
||||
|
||||
The slide is exposed as the raw `Rv\Data\Slide` protobuf because stage layouts
|
||||
can contain complex arrangements.
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-stage.php /path/to/Stage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/StageFileReader.php` | Reads the `Stage` file |
|
||||
| `src/StageFileWriter.php` | Writes the `Stage` file |
|
||||
| `src/StageLibrary.php` | Document wrapper and indexes |
|
||||
| `src/StageLayout.php` | Single layout wrapper |
|
||||
| `bin/parse-stage.php` | CLI summary tool |
|
||||
106
doc/api/test-patterns.md
Normal file
106
doc/api/test-patterns.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# TestPatterns Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter `TestPatterns` file
|
||||
> (raw protobuf, no extension), including selected test-pattern state and saved
|
||||
> pattern definitions.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\TestPatternsFileReader;
|
||||
use ProPresenter\Parser\TestPatternsFileWriter;
|
||||
|
||||
$library = TestPatternsFileReader::read('/path/to/TestPatterns');
|
||||
|
||||
$library->getDisplayLocation(); // 3
|
||||
$library->getSpecificScreenUuid(); // "BCDE1115-..."
|
||||
$library->getPatterns(); // TestPatternData[]
|
||||
|
||||
TestPatternsFileWriter::write($library, '/path/to/TestPatterns');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `TestPatterns` file is the protobuf-serialised
|
||||
[`TestPatternDocument`](../../proto/testPattern.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `state` | `TestPatternDocument.TestPatternStateData` | Current test-pattern display state |
|
||||
| `patterns` | repeated `TestPatternDocument.TestPatternData` | Saved pattern definitions |
|
||||
|
||||
`TestPatternStateData` includes selected pattern UUID/name, display location,
|
||||
specific screen UUID, identify-screen flag, logo type, and optional user logo.
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\TestPatternsFileReader;
|
||||
|
||||
$library = TestPatternsFileReader::read('/Users/me/.../TestPatterns');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## TestPatternsLibrary
|
||||
|
||||
Top-level wrapper around `Rv\Data\TestPatternDocument`. Indexes saved pattern
|
||||
definitions by UUID (case-insensitive) and localization key.
|
||||
|
||||
```php
|
||||
$library->getState();
|
||||
$library->setState($stateOrNull);
|
||||
$library->getSelectedPatternUuid();
|
||||
$library->getSelectedPatternNameLocalizationKey();
|
||||
$library->getDisplayLocation();
|
||||
$library->getSpecificScreenUuid();
|
||||
$library->getPatterns();
|
||||
$library->count();
|
||||
$library->getPatternByUuid('...');
|
||||
$library->getPatternByName('Test Pattern');
|
||||
$library->addPattern('Test Pattern', 'UUID');
|
||||
$library->removePattern('UUID');
|
||||
$library->getDocument(); // \Rv\Data\TestPatternDocument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-test-patterns.php /path/to/TestPatterns
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
TestPatterns (0):
|
||||
State: selected=(none) :: name=(none) :: display_location=3 :: screen=BCDE1115-AD40-4BA4-A33A-BFFE3E87223B
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/TestPatternsLibrary.php` | Document-level wrapper with state accessors |
|
||||
| `src/TestPatternsFileReader.php` | Reads the `TestPatterns` file |
|
||||
| `src/TestPatternsFileWriter.php` | Writes the `TestPatterns` file |
|
||||
| `bin/parse-test-patterns.php` | CLI tool |
|
||||
| `proto/testPattern.proto` | Protobuf schema |
|
||||
| `generated/Rv/Data/TestPatternDocument.php` | Generated message class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
The wrapper exposes `TestPatternData` and `TestPatternStateData` protobufs
|
||||
directly. It does not render test patterns or interpret nested property oneofs.
|
||||
116
doc/api/theme.md
Normal file
116
doc/api/theme.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Theme Bundle API
|
||||
|
||||
> PHP module for reading, modifying, and writing folder-based ProPresenter themes.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\ThemeFileReader;
|
||||
use ProPresenter\Parser\ThemeFileWriter;
|
||||
|
||||
$theme = ThemeFileReader::read('/path/to/theme-folder');
|
||||
|
||||
foreach ($theme->getSlides() as $slide) {
|
||||
$slide->getName(); // KeyVisual, Liedtext, ...
|
||||
$slide->getBaseSlide(); // ?\Rv\Data\Slide
|
||||
}
|
||||
|
||||
foreach ($theme->getAssets() as $asset) {
|
||||
$asset->getName();
|
||||
$asset->getSize();
|
||||
$asset->getMimeType();
|
||||
}
|
||||
|
||||
ThemeFileWriter::write($theme, '/path/to/output-folder');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Folder Layout
|
||||
|
||||
A theme is a directory, not a single protobuf file:
|
||||
|
||||
```text
|
||||
SampleTheme/
|
||||
├── Theme # Rv\Data\Template\Document protobuf
|
||||
└── Assets/
|
||||
├── BACKGROUND.jpg
|
||||
├── BAUCHBIND_STREAM.jpg
|
||||
└── KEY_VISUAL.jpg
|
||||
```
|
||||
|
||||
The `Theme` file is a `Rv\Data\Template\Document` from `template.proto`.
|
||||
Its slides are named theme layouts.
|
||||
|
||||
---
|
||||
|
||||
## ThemeBundle
|
||||
|
||||
```php
|
||||
$theme->getDocument();
|
||||
$theme->getSlides();
|
||||
$theme->getSlideByName('KeyVisual');
|
||||
$theme->addSlide($slide);
|
||||
$theme->removeSlide('KeyVisual');
|
||||
$theme->getAssets();
|
||||
$theme->getAssetByName('BACKGROUND.jpg');
|
||||
$theme->addAsset('NEW.jpg', $bytes);
|
||||
$theme->removeAsset('NEW.jpg');
|
||||
$theme->count();
|
||||
$theme->getAssetCount();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ThemeSlide
|
||||
|
||||
```php
|
||||
$slide->getName();
|
||||
$slide->setName('Liedtext');
|
||||
$slide->getBaseSlide();
|
||||
$slide->getProto();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ThemeAsset
|
||||
|
||||
```php
|
||||
$asset->getName();
|
||||
$asset->getBytes();
|
||||
$asset->getSize();
|
||||
$asset->getMimeType(); // image/jpeg, image/png, ...
|
||||
```
|
||||
|
||||
MIME type detection is extension-based and best-effort.
|
||||
|
||||
---
|
||||
|
||||
## Writing Themes
|
||||
|
||||
`ThemeFileWriter::write()` creates the target folder if needed, writes the
|
||||
serialized `Theme` protobuf, creates `Assets/`, writes every `ThemeAsset`, and
|
||||
removes stale files from `Assets/` that are not present in the bundle.
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-theme.php /path/to/theme-folder
|
||||
```
|
||||
|
||||
The CLI prints slide names plus asset filenames, sizes, and MIME types.
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/ThemeBundle.php` | Top-level theme wrapper |
|
||||
| `src/ThemeFileReader.php` | Reads a theme folder |
|
||||
| `src/ThemeFileWriter.php` | Writes a theme folder and cleans stale assets |
|
||||
| `src/ThemeSlide.php` | Single template slide wrapper |
|
||||
| `src/ThemeAsset.php` | Single asset value object |
|
||||
| `bin/parse-theme.php` | CLI summary tool |
|
||||
106
doc/api/timers.md
Normal file
106
doc/api/timers.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Timers Library API
|
||||
|
||||
> PHP module for reading and writing the global ProPresenter `Timers` file
|
||||
> (raw protobuf, no extension), including the top-level clock format.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\TimersFileReader;
|
||||
|
||||
$library = TimersFileReader::read('/path/to/Timers');
|
||||
$library->getClockFormat(); // "HH:mm"
|
||||
|
||||
foreach ($library->getTimers() as $timer) {
|
||||
$timer->getName();
|
||||
$timer->isCountdown();
|
||||
$timer->getDurationSeconds();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `Timers` file is the protobuf-serialised
|
||||
[`TimersDocument`](../../proto/timers.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `application_info` | `ApplicationInfo` | ProPresenter metadata |
|
||||
| `clock` | `Clock` | Global clock display settings |
|
||||
| `timers` | repeated `Timer` | Timer definitions |
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
$library = TimersFileReader::read('/Users/me/.../Timers');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## TimersLibrary
|
||||
|
||||
```php
|
||||
$library->getTimers(); // Timer[]
|
||||
$library->count(); // int
|
||||
$library->getTimerByUuid($uuid); // ?Timer (case-insensitive)
|
||||
$library->getTimerByName($name); // ?Timer
|
||||
$library->addTimer($name, $uuid); // Timer
|
||||
$library->removeTimer($uuid); // bool
|
||||
$library->getClockFormat(); // string
|
||||
$library->setClockFormat('HH:mm'); // void
|
||||
$library->getApplicationInfo(); // ?\Rv\Data\ApplicationInfo
|
||||
$library->getDocument(); // \Rv\Data\TimersDocument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Timer
|
||||
|
||||
```php
|
||||
$timer->getUuid();
|
||||
$timer->setUuid($uuid);
|
||||
$timer->getName();
|
||||
$timer->setName($name);
|
||||
$timer->getConfiguration(); // ?\Rv\Data\Timer\Configuration
|
||||
$timer->isCountdown();
|
||||
$timer->isCountdownToTime();
|
||||
$timer->isElapsedTime();
|
||||
$timer->getDurationSeconds(); // ?int for countdown timers
|
||||
$timer->getProto(); // \Rv\Data\Timer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-timers.php /path/to/Timers
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/TimersLibrary.php` | Document wrapper with UUID / name indexes |
|
||||
| `src/Timer.php` | Single timer wrapper |
|
||||
| `src/TimersFileReader.php` | Reads the `Timers` file |
|
||||
| `src/TimersFileWriter.php` | Writes the `Timers` file |
|
||||
| `bin/parse-timers.php` | CLI summary tool |
|
||||
| `generated/Rv/Data/TimersDocument.php` | Generated document class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
Timer configuration is exposed as the generated protobuf sub-message. Helper
|
||||
methods cover the oneof timer type and countdown duration without hiding raw
|
||||
access for callers that need advanced fields.
|
||||
83
doc/api/workspace.md
Normal file
83
doc/api/workspace.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Workspace Library API
|
||||
|
||||
> PHP module for reading, modifying, and writing the ProPresenter `Workspace` file.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\WorkspaceFileReader;
|
||||
use ProPresenter\Parser\WorkspaceFileWriter;
|
||||
|
||||
$library = WorkspaceFileReader::read('/path/to/Workspace');
|
||||
|
||||
foreach ($library->getScreens() as $screen) {
|
||||
$screen->getName();
|
||||
$screen->getUuid();
|
||||
$screen->getScreenType();
|
||||
}
|
||||
|
||||
WorkspaceFileWriter::write($library, '/path/to/Workspace');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `Workspace` file is `Rv\Data\ProPresenterWorkspace` from `proworkspace.proto`.
|
||||
Its `pro_screens` entries are `Rv\Data\ProPresenterScreen` messages from
|
||||
`proscreen.proto`.
|
||||
|
||||
---
|
||||
|
||||
## WorkspaceLibrary
|
||||
|
||||
```php
|
||||
$library->getDocument();
|
||||
$library->getScreens();
|
||||
$library->getScreenByName('StageDisplay');
|
||||
$library->getScreenByUuid('C86D...'); // case-insensitive
|
||||
$library->addScreen($screen);
|
||||
$library->removeScreen($uuid);
|
||||
$library->count();
|
||||
$library->getAudienceLooks();
|
||||
$library->getMasks();
|
||||
$library->getVideoInputs();
|
||||
$library->getSelectedLibraryName();
|
||||
$library->setSelectedLibraryName('Library');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Screen
|
||||
|
||||
```php
|
||||
$screen->getName();
|
||||
$screen->setName('New name');
|
||||
$screen->getUuid();
|
||||
$screen->setUuid('...');
|
||||
$screen->getScreenType();
|
||||
$screen->getIndex();
|
||||
$screen->getProto();
|
||||
```
|
||||
|
||||
Use `getProto()` for detailed arrangement, background, and screen geometry data.
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-workspace.php /path/to/Workspace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/WorkspaceFileReader.php` | Reads the `Workspace` file |
|
||||
| `src/WorkspaceFileWriter.php` | Writes the `Workspace` file |
|
||||
| `src/WorkspaceLibrary.php` | Document wrapper and indexes |
|
||||
| `src/Screen.php` | Single screen wrapper |
|
||||
| `bin/parse-workspace.php` | CLI summary tool |
|
||||
103
doc/keywords.md
103
doc/keywords.md
|
|
@ -13,30 +13,34 @@
|
|||
| ZIP | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md), [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) |
|
||||
| ZIP64 | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md), [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
|
||||
| binary format | [formats/pp_song_spec.md](formats/pp_song_spec.md), [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md), [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
|
||||
| JSON | [api/communication-devices.md](api/communication-devices.md) |
|
||||
|
||||
## Song Structure
|
||||
|
||||
| Keyword | Document |
|
||||
|---------|----------|
|
||||
| song | [api/song.md](api/song.md) |
|
||||
| group | [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 4 |
|
||||
| group | [api/song.md](api/song.md), [api/groups.md](api/groups.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 4 |
|
||||
| slide | [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| arrangement | [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 6 |
|
||||
| translation | [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 7 |
|
||||
| verse | [api/song.md](api/song.md) |
|
||||
| chorus | [api/song.md](api/song.md) |
|
||||
| lyrics | [api/song.md](api/song.md) |
|
||||
| CCLI | [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 3 |
|
||||
| CCLI | [api/ccli.md](api/ccli.md), [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 3 |
|
||||
|
||||
## Bundle Structure
|
||||
|
||||
| Keyword | Document |
|
||||
|---------|----------|
|
||||
| bundle | [api/bundle.md](api/bundle.md), [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
|
||||
| bundle | [api/bundle.md](api/bundle.md), [api/theme.md](api/theme.md), [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
|
||||
| probundle | [api/bundle.md](api/bundle.md), [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
|
||||
| pro6x | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 1 |
|
||||
| LocalRelativePath | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 3 |
|
||||
| absolute path | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 2 |
|
||||
| theme | [api/theme.md](api/theme.md) |
|
||||
| theme folder | [api/theme.md](api/theme.md) |
|
||||
| assets | [api/theme.md](api/theme.md), [api/bundle.md](api/bundle.md) |
|
||||
|
||||
## Playlist Structure
|
||||
|
||||
|
|
@ -70,25 +74,86 @@
|
|||
| Macros file | [api/macros.md](api/macros.md) |
|
||||
| MacroCollection | [api/macros.md](api/macros.md) |
|
||||
| MacroLibrary | [api/macros.md](api/macros.md) |
|
||||
| media | [api/song.md](api/song.md), [api/bundle.md](api/bundle.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 3 |
|
||||
| image | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 3 |
|
||||
| media | [api/song.md](api/song.md), [api/bundle.md](api/bundle.md), [api/theme.md](api/theme.md) |
|
||||
| image | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 3, [api/theme.md](api/theme.md) |
|
||||
| video | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 6 |
|
||||
| cue | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| cue | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [api/props.md](api/props.md) |
|
||||
| label | [api/labels.md](api/labels.md), [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| Labels file | [api/labels.md](api/labels.md) |
|
||||
| LabelLibrary | [api/labels.md](api/labels.md) |
|
||||
| LabelsFileReader | [api/labels.md](api/labels.md) |
|
||||
| color | [api/labels.md](api/labels.md), [api/macros.md](api/macros.md) |
|
||||
| color | [api/labels.md](api/labels.md), [api/macros.md](api/macros.md), [api/groups.md](api/groups.md), [api/clear-groups.md](api/clear-groups.md) |
|
||||
|
||||
## Global library files
|
||||
|
||||
| Keyword | Document |
|
||||
|---------|----------|
|
||||
| Groups file | [api/groups.md](api/groups.md) |
|
||||
| GroupLibrary | [api/groups.md](api/groups.md) |
|
||||
| GroupDefinition | [api/groups.md](api/groups.md) |
|
||||
| ClearGroups file | [api/clear-groups.md](api/clear-groups.md) |
|
||||
| ClearGroup | [api/clear-groups.md](api/clear-groups.md) |
|
||||
| ClearGroupDefinition | [api/clear-groups.md](api/clear-groups.md) |
|
||||
| CCLI file | [api/ccli.md](api/ccli.md) |
|
||||
| CCLILibrary | [api/ccli.md](api/ccli.md) |
|
||||
| copyright | [api/ccli.md](api/ccli.md) |
|
||||
| license | [api/ccli.md](api/ccli.md) |
|
||||
| Messages file | [api/messages.md](api/messages.md) |
|
||||
| MessageLibrary | [api/messages.md](api/messages.md) |
|
||||
| Message | [api/messages.md](api/messages.md) |
|
||||
| token | [api/messages.md](api/messages.md) |
|
||||
| Timers file | [api/timers.md](api/timers.md) |
|
||||
| TimersLibrary | [api/timers.md](api/timers.md) |
|
||||
| Timer | [api/timers.md](api/timers.md) |
|
||||
| Clock | [api/timers.md](api/timers.md) |
|
||||
| countdown | [api/timers.md](api/timers.md) |
|
||||
| Stage file | [api/stage.md](api/stage.md) |
|
||||
| StageLibrary | [api/stage.md](api/stage.md) |
|
||||
| StageLayout | [api/stage.md](api/stage.md) |
|
||||
| stage display | [api/stage.md](api/stage.md) |
|
||||
| Workspace file | [api/workspace.md](api/workspace.md) |
|
||||
| WorkspaceLibrary | [api/workspace.md](api/workspace.md) |
|
||||
| Screen | [api/workspace.md](api/workspace.md) |
|
||||
| audience look | [api/workspace.md](api/workspace.md) |
|
||||
| mask | [api/workspace.md](api/workspace.md) |
|
||||
| video input | [api/workspace.md](api/workspace.md) |
|
||||
| Props file | [api/props.md](api/props.md) |
|
||||
| PropLibrary | [api/props.md](api/props.md) |
|
||||
| Prop | [api/props.md](api/props.md) |
|
||||
| TestPatterns file | [api/test-patterns.md](api/test-patterns.md) |
|
||||
| TestPatternsLibrary | [api/test-patterns.md](api/test-patterns.md) |
|
||||
| test pattern | [api/test-patterns.md](api/test-patterns.md) |
|
||||
| Calendar file | [api/calendar.md](api/calendar.md) |
|
||||
| CalendarLibrary | [api/calendar.md](api/calendar.md) |
|
||||
| CalendarEvent | [api/calendar.md](api/calendar.md) |
|
||||
| schedule | [api/calendar.md](api/calendar.md) |
|
||||
| KeyMappings file | [api/key-mappings.md](api/key-mappings.md) |
|
||||
| KeyMappingsLibrary | [api/key-mappings.md](api/key-mappings.md) |
|
||||
| KeyMapping | [api/key-mappings.md](api/key-mappings.md) |
|
||||
| hot key | [api/key-mappings.md](api/key-mappings.md), [api/groups.md](api/groups.md) |
|
||||
| CommunicationDevices file | [api/communication-devices.md](api/communication-devices.md) |
|
||||
| CommunicationDevicesLibrary | [api/communication-devices.md](api/communication-devices.md) |
|
||||
| CommunicationDevice | [api/communication-devices.md](api/communication-devices.md) |
|
||||
| MIDI | [api/communication-devices.md](api/communication-devices.md) |
|
||||
| OSC | [api/communication-devices.md](api/communication-devices.md) |
|
||||
| Theme | [api/theme.md](api/theme.md) |
|
||||
| ThemeBundle | [api/theme.md](api/theme.md) |
|
||||
| ThemeSlide | [api/theme.md](api/theme.md) |
|
||||
| ThemeAsset | [api/theme.md](api/theme.md) |
|
||||
|
||||
## PHP API
|
||||
|
||||
| Keyword | Document |
|
||||
|---------|----------|
|
||||
| read | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
| write | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md) |
|
||||
| read | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md), [api/groups.md](api/groups.md) |
|
||||
| write | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md), [api/groups.md](api/groups.md), [api/theme.md](api/theme.md) |
|
||||
| generate | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md) |
|
||||
| parse | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
| parse | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md), [api/groups.md](api/groups.md) |
|
||||
| MacrosFileReader | [api/macros.md](api/macros.md) |
|
||||
| MacrosFileWriter | [api/macros.md](api/macros.md) |
|
||||
| LabelsFileWriter | [api/labels.md](api/labels.md) |
|
||||
| GroupsFileReader | [api/groups.md](api/groups.md) |
|
||||
| GroupsFileWriter | [api/groups.md](api/groups.md) |
|
||||
| ProFileReader | [api/song.md](api/song.md) |
|
||||
| ProFileWriter | [api/song.md](api/song.md) |
|
||||
| ProFileGenerator | [api/song.md](api/song.md) |
|
||||
|
|
@ -98,9 +163,11 @@
|
|||
| ProBundleReader | [api/bundle.md](api/bundle.md) |
|
||||
| ProBundleWriter | [api/bundle.md](api/bundle.md) |
|
||||
| PresentationBundle | [api/bundle.md](api/bundle.md) |
|
||||
| ThemeFileReader | [api/theme.md](api/theme.md) |
|
||||
| ThemeFileWriter | [api/theme.md](api/theme.md) |
|
||||
| Song | [api/song.md](api/song.md) |
|
||||
| PlaylistArchive | [api/playlist.md](api/playlist.md) |
|
||||
| CLI | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
| CLI | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md), [api/groups.md](api/groups.md) |
|
||||
| command line | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
|
||||
## Protobuf
|
||||
|
|
@ -109,13 +176,25 @@
|
|||
|---------|----------|
|
||||
| Presentation | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 3 |
|
||||
| CueGroup | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 4 |
|
||||
| Cue | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| Cue | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [api/props.md](api/props.md) |
|
||||
| Action | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| Playlist | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) Section 3 |
|
||||
| PlaylistItem | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) Section 5 |
|
||||
| UUID | [formats/pp_song_spec.md](formats/pp_song_spec.md), [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) |
|
||||
| field number | [formats/pp_song_spec.md](formats/pp_song_spec.md) Appendix |
|
||||
| proto | [formats/pp_song_spec.md](formats/pp_song_spec.md) |
|
||||
| Template.Document | [api/theme.md](api/theme.md) |
|
||||
| ProPresenterWorkspace | [api/workspace.md](api/workspace.md) |
|
||||
| ProGroupsDocument | [api/groups.md](api/groups.md) |
|
||||
| ClearGroupsDocument | [api/clear-groups.md](api/clear-groups.md) |
|
||||
| MessageDocument | [api/messages.md](api/messages.md) |
|
||||
| TimersDocument | [api/timers.md](api/timers.md) |
|
||||
| Stage.Document | [api/stage.md](api/stage.md) |
|
||||
| PropDocument | [api/props.md](api/props.md) |
|
||||
| TestPatternDocument | [api/test-patterns.md](api/test-patterns.md) |
|
||||
| CalendarDocument | [api/calendar.md](api/calendar.md) |
|
||||
| KeyMappingsDocument | [api/key-mappings.md](api/key-mappings.md) |
|
||||
| CCLIDocument | [api/ccli.md](api/ccli.md) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
|||
BIN
doc/reference_samples/CCLI
Normal file
BIN
doc/reference_samples/CCLI
Normal file
Binary file not shown.
49
doc/reference_samples/Calendar
Normal file
49
doc/reference_samples/Calendar
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
ƒ
|
||||
&
|
||||
$3E749EF4-0663-4F0F-AACA-BD801B6D8ACD
|
||||
Doors Open"ŒÚæ¹*2ü²‹ºÙ€¡JBzf"v
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimerJ¸
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:450HÂv
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimer
|
||||
ù
|
||||
&
|
||||
$794A1711-84DB-42F6-8012-B43B1F395096
|
||||
Godi Start"ˆâæ¹*B{f"w
|
||||
u
|
||||
&
|
||||
$36C9EB9B-E9B0-4D57-95CD-5E4956AC2AF9Godi START - 10:027
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimerJº
|
||||
&
|
||||
$36C9EB9B-E9B0-4D57-95CD-5E4956AC2AF9Godi START - 10:020HÂw
|
||||
u
|
||||
&
|
||||
$36C9EB9B-E9B0-4D57-95CD-5E4956AC2AF9Godi START - 10:027
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimer
|
||||
ó
|
||||
&
|
||||
$81394F5E-E776-458B-BB06-5C87B3C46D94
|
||||
Doors Open"Œ¬†ºBzf"v
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimerJ¸
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:450HÂv
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimer
|
||||
BIN
doc/reference_samples/ClearGroups
Normal file
BIN
doc/reference_samples/ClearGroups
Normal file
Binary file not shown.
1
doc/reference_samples/CommunicationDevices
Normal file
1
doc/reference_samples/CommunicationDevices
Normal file
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
BIN
doc/reference_samples/Groups
Normal file
BIN
doc/reference_samples/Groups
Normal file
Binary file not shown.
2
doc/reference_samples/KeyMappings
Normal file
2
doc/reference_samples/KeyMappings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
"" 117899279
|
||||
BIN
doc/reference_samples/Messages
Normal file
BIN
doc/reference_samples/Messages
Normal file
Binary file not shown.
BIN
doc/reference_samples/Props
Normal file
BIN
doc/reference_samples/Props
Normal file
Binary file not shown.
BIN
doc/reference_samples/Stage
Normal file
BIN
doc/reference_samples/Stage
Normal file
Binary file not shown.
3
doc/reference_samples/TestPatterns
Normal file
3
doc/reference_samples/TestPatterns
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
*"&
|
||||
$BCDE1115-AD40-4BA4-A33A-BFFE3E87223B
|
||||
BIN
doc/reference_samples/Timers
Normal file
BIN
doc/reference_samples/Timers
Normal file
Binary file not shown.
BIN
doc/reference_samples/Workspace
Normal file
BIN
doc/reference_samples/Workspace
Normal file
Binary file not shown.
BIN
doc/reference_samples/pp-config/CCLI
Normal file
BIN
doc/reference_samples/pp-config/CCLI
Normal file
Binary file not shown.
49
doc/reference_samples/pp-config/Calendar
Normal file
49
doc/reference_samples/pp-config/Calendar
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
ƒ
|
||||
&
|
||||
$3E749EF4-0663-4F0F-AACA-BD801B6D8ACD
|
||||
Doors Open"ŒÚæ¹*2ü²‹ºÙ€¡JBzf"v
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimerJ¸
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:450HÂv
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimer
|
||||
ù
|
||||
&
|
||||
$794A1711-84DB-42F6-8012-B43B1F395096
|
||||
Godi Start"ˆâæ¹*B{f"w
|
||||
u
|
||||
&
|
||||
$36C9EB9B-E9B0-4D57-95CD-5E4956AC2AF9Godi START - 10:027
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimerJº
|
||||
&
|
||||
$36C9EB9B-E9B0-4D57-95CD-5E4956AC2AF9Godi START - 10:020HÂw
|
||||
u
|
||||
&
|
||||
$36C9EB9B-E9B0-4D57-95CD-5E4956AC2AF9Godi START - 10:027
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimer
|
||||
ó
|
||||
&
|
||||
$81394F5E-E776-458B-BB06-5C87B3C46D94
|
||||
Doors Open"Œ¬†ºBzf"v
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimerJ¸
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:450HÂv
|
||||
t
|
||||
&
|
||||
$267A1234-C307-4A2A-ADC4-2A1F53583378Doors Open - 9:457
|
||||
&
|
||||
$AD18A4F6-135F-4A52-B92B-CA6619A55A9B
AbsoluteTimer
|
||||
BIN
doc/reference_samples/pp-config/ClearGroups
Normal file
BIN
doc/reference_samples/pp-config/ClearGroups
Normal file
Binary file not shown.
1
doc/reference_samples/pp-config/CommunicationDevices
Normal file
1
doc/reference_samples/pp-config/CommunicationDevices
Normal file
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
BIN
doc/reference_samples/pp-config/Groups
Normal file
BIN
doc/reference_samples/pp-config/Groups
Normal file
Binary file not shown.
2
doc/reference_samples/pp-config/KeyMappings
Normal file
2
doc/reference_samples/pp-config/KeyMappings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
"" 117899279
|
||||
BIN
doc/reference_samples/pp-config/Labels
Normal file
BIN
doc/reference_samples/pp-config/Labels
Normal file
Binary file not shown.
BIN
doc/reference_samples/pp-config/Macros
Normal file
BIN
doc/reference_samples/pp-config/Macros
Normal file
Binary file not shown.
BIN
doc/reference_samples/pp-config/Messages
Normal file
BIN
doc/reference_samples/pp-config/Messages
Normal file
Binary file not shown.
BIN
doc/reference_samples/pp-config/Props
Normal file
BIN
doc/reference_samples/pp-config/Props
Normal file
Binary file not shown.
BIN
doc/reference_samples/pp-config/Stage
Normal file
BIN
doc/reference_samples/pp-config/Stage
Normal file
Binary file not shown.
3
doc/reference_samples/pp-config/TestPatterns
Normal file
3
doc/reference_samples/pp-config/TestPatterns
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
*"&
|
||||
$BCDE1115-AD40-4BA4-A33A-BFFE3E87223B
|
||||
BIN
doc/reference_samples/pp-config/Timers
Normal file
BIN
doc/reference_samples/pp-config/Timers
Normal file
Binary file not shown.
BIN
doc/reference_samples/pp-config/Workspace
Normal file
BIN
doc/reference_samples/pp-config/Workspace
Normal file
Binary file not shown.
BIN
doc/reference_samples/pp-themes/sample/Assets/BACKGROUND.jpg
Normal file
BIN
doc/reference_samples/pp-themes/sample/Assets/BACKGROUND.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
doc/reference_samples/pp-themes/sample/Assets/KEY_VISUAL.jpg
Normal file
BIN
doc/reference_samples/pp-themes/sample/Assets/KEY_VISUAL.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
BIN
doc/reference_samples/pp-themes/sample/Theme
Normal file
BIN
doc/reference_samples/pp-themes/sample/Theme
Normal file
Binary file not shown.
27
generated/GPBMetadata/Calendar.php
Normal file
27
generated/GPBMetadata/Calendar.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: calendar.proto
|
||||
|
||||
namespace GPBMetadata;
|
||||
|
||||
class Calendar
|
||||
{
|
||||
public static $is_initialized = false;
|
||||
|
||||
public static function initOnce() {
|
||||
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
|
||||
|
||||
if (static::$is_initialized == true) {
|
||||
return;
|
||||
}
|
||||
\GPBMetadata\Rvtimestamp::initOnce();
|
||||
\GPBMetadata\Uuid::initOnce();
|
||||
$pool->internalAddGeneratedFile(
|
||||
"\x0A\xF2\x02\x0A\x0Ecalendar.proto\x12\x07rv.data\x1A\x0Auuid.proto\"\x8C\x02\x0A\x10CalendarDocument\x12/\x0A\x06events\x18\x01 \x03(\x0B2\x1F.rv.data.CalendarDocument.Event\x12\x0C\x0A\x04mode\x18\x02 \x01(\x0D\x1A\xB8\x01\x0A\x05Event\x12\x1B\x0A\x04uuid\x18\x01 \x01(\x0B2\x0D.rv.data.UUID\x12\x0C\x0A\x04name\x18\x02 \x01(\x09\x12&\x0A\x0Astart_time\x18\x04 \x01(\x0B2\x12.rv.data.Timestamp\x12\x0D\x0A\x05flags\x18\x05 \x01(\x0C\x12\$\x0A\x08end_time\x18\x06 \x01(\x0B2\x12.rv.data.Timestamp\x12\x13\x0A\x0Baction_data\x18\x08 \x01(\x0C\x12\x12\x0A\x0Amacro_data\x18\x09 \x01(\x0CB4\xF8\x01\x01\xAA\x02\$Pro.SerializationInterop.RVProtoData\xBA\x02\x07RVData_b\x06proto3"
|
||||
, true);
|
||||
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
28
generated/GPBMetadata/KeyMappings.php
Normal file
28
generated/GPBMetadata/KeyMappings.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: keyMappings.proto
|
||||
|
||||
namespace GPBMetadata;
|
||||
|
||||
class KeyMappings
|
||||
{
|
||||
public static $is_initialized = false;
|
||||
|
||||
public static function initOnce() {
|
||||
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
|
||||
|
||||
if (static::$is_initialized == true) {
|
||||
return;
|
||||
}
|
||||
\GPBMetadata\ApplicationInfo::initOnce();
|
||||
\GPBMetadata\HotKey::initOnce();
|
||||
\GPBMetadata\Uuid::initOnce();
|
||||
$pool->internalAddGeneratedFile(
|
||||
"\x0A\xE0\x02\x0A\x11keyMappings.proto\x12\x07rv.data\x1A\x0ChotKey.proto\x1A\x0Auuid.proto\"\xE9\x01\x0A\x13KeyMappingsDocument\x122\x0A\x10application_info\x18\x01 \x01(\x0B2\x18.rv.data.ApplicationInfo\x126\x0A\x08mappings\x18\x02 \x03(\x0B2\$.rv.data.KeyMappingsDocument.Mapping\x1Af\x0A\x07Mapping\x12\x1B\x0A\x04uuid\x18\x01 \x01(\x0B2\x0D.rv.data.UUID\x12 \x0A\x07hot_key\x18\x02 \x01(\x0B2\x0F.rv.data.HotKey\x12\x0E\x0A\x06target\x18\x03 \x01(\x0C\x12\x0C\x0A\x04name\x18\x04 \x01(\x09B4\xF8\x01\x01\xAA\x02\$Pro.SerializationInterop.RVProtoData\xBA\x02\x07RVData_b\x06proto3"
|
||||
, true);
|
||||
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
107
generated/Rv/Data/CalendarDocument.php
Normal file
107
generated/Rv/Data/CalendarDocument.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: calendar.proto
|
||||
|
||||
namespace Rv\Data;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* CalendarDocument is the global ProPresenter `Calendar` file. It holds
|
||||
* scheduled events that fire macros at given times. The exact semantics of
|
||||
* the inner sub-messages 8 and 9 (action and embedded macro reference) are
|
||||
* preserved as raw bytes so the document round-trips byte-for-byte; clients
|
||||
* that need to inspect them can decode them with `protoc --decode_raw` or
|
||||
* reach for the raw protobuf message.
|
||||
*
|
||||
* Generated from protobuf message <code>rv.data.CalendarDocument</code>
|
||||
*/
|
||||
class CalendarDocument extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Events scheduled in the calendar, in the order ProPresenter wrote them.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .rv.data.CalendarDocument.Event events = 1;</code>
|
||||
*/
|
||||
private $events;
|
||||
/**
|
||||
* Source / mode flag observed in samples (value `1`). Treated as opaque.
|
||||
*
|
||||
* Generated from protobuf field <code>uint32 mode = 2;</code>
|
||||
*/
|
||||
protected $mode = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type array<\Rv\Data\CalendarDocument\Event>|\Google\Protobuf\Internal\RepeatedField $events
|
||||
* Events scheduled in the calendar, in the order ProPresenter wrote them.
|
||||
* @type int $mode
|
||||
* Source / mode flag observed in samples (value `1`). Treated as opaque.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Calendar::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Events scheduled in the calendar, in the order ProPresenter wrote them.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .rv.data.CalendarDocument.Event events = 1;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getEvents()
|
||||
{
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Events scheduled in the calendar, in the order ProPresenter wrote them.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .rv.data.CalendarDocument.Event events = 1;</code>
|
||||
* @param array<\Rv\Data\CalendarDocument\Event>|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setEvents($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Rv\Data\CalendarDocument\Event::class);
|
||||
$this->events = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source / mode flag observed in samples (value `1`). Treated as opaque.
|
||||
*
|
||||
* Generated from protobuf field <code>uint32 mode = 2;</code>
|
||||
* @return int
|
||||
*/
|
||||
public function getMode()
|
||||
{
|
||||
return $this->mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source / mode flag observed in samples (value `1`). Treated as opaque.
|
||||
*
|
||||
* Generated from protobuf field <code>uint32 mode = 2;</code>
|
||||
* @param int $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setMode($var)
|
||||
{
|
||||
GPBUtil::checkUint32($var);
|
||||
$this->mode = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
316
generated/Rv/Data/CalendarDocument/Event.php
Normal file
316
generated/Rv/Data/CalendarDocument/Event.php
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: calendar.proto
|
||||
|
||||
namespace Rv\Data\CalendarDocument;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Generated from protobuf message <code>rv.data.CalendarDocument.Event</code>
|
||||
*/
|
||||
class Event extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Stable identity of this calendar event.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.UUID uuid = 1;</code>
|
||||
*/
|
||||
protected $uuid = null;
|
||||
/**
|
||||
* Display name (e.g. "Doors Open").
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 2;</code>
|
||||
*/
|
||||
protected $name = '';
|
||||
/**
|
||||
* When the event starts. Stored as Timestamp seconds (and optional nanos).
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.Timestamp start_time = 4;</code>
|
||||
*/
|
||||
protected $start_time = null;
|
||||
/**
|
||||
* Opaque flags blob observed in samples. Often a single byte (e.g. 0x01)
|
||||
* that ProPresenter uses for recurrence or visibility state.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes flags = 5;</code>
|
||||
*/
|
||||
protected $flags = '';
|
||||
/**
|
||||
* When the event ends. Optional. Same format as `start_time`.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.Timestamp end_time = 6;</code>
|
||||
*/
|
||||
protected $end_time = null;
|
||||
/**
|
||||
* Action that runs when the event fires. Encoded as raw protobuf bytes
|
||||
* (a `rv.data.Action`-shaped message) so the schema can evolve without
|
||||
* breaking existing clients.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes action_data = 8;</code>
|
||||
*/
|
||||
protected $action_data = '';
|
||||
/**
|
||||
* Embedded copy of the macro definition the event triggers. Stored as
|
||||
* raw protobuf bytes (shape compatible with `MacrosDocument.Macro`).
|
||||
*
|
||||
* Generated from protobuf field <code>bytes macro_data = 9;</code>
|
||||
*/
|
||||
protected $macro_data = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type \Rv\Data\UUID $uuid
|
||||
* Stable identity of this calendar event.
|
||||
* @type string $name
|
||||
* Display name (e.g. "Doors Open").
|
||||
* @type \Rv\Data\Timestamp $start_time
|
||||
* When the event starts. Stored as Timestamp seconds (and optional nanos).
|
||||
* @type string $flags
|
||||
* Opaque flags blob observed in samples. Often a single byte (e.g. 0x01)
|
||||
* that ProPresenter uses for recurrence or visibility state.
|
||||
* @type \Rv\Data\Timestamp $end_time
|
||||
* When the event ends. Optional. Same format as `start_time`.
|
||||
* @type string $action_data
|
||||
* Action that runs when the event fires. Encoded as raw protobuf bytes
|
||||
* (a `rv.data.Action`-shaped message) so the schema can evolve without
|
||||
* breaking existing clients.
|
||||
* @type string $macro_data
|
||||
* Embedded copy of the macro definition the event triggers. Stored as
|
||||
* raw protobuf bytes (shape compatible with `MacrosDocument.Macro`).
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Calendar::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable identity of this calendar event.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.UUID uuid = 1;</code>
|
||||
* @return \Rv\Data\UUID|null
|
||||
*/
|
||||
public function getUuid()
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function hasUuid()
|
||||
{
|
||||
return isset($this->uuid);
|
||||
}
|
||||
|
||||
public function clearUuid()
|
||||
{
|
||||
unset($this->uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable identity of this calendar event.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.UUID uuid = 1;</code>
|
||||
* @param \Rv\Data\UUID $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setUuid($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Rv\Data\UUID::class);
|
||||
$this->uuid = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name (e.g. "Doors Open").
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 2;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name (e.g. "Doors Open").
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 2;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->name = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the event starts. Stored as Timestamp seconds (and optional nanos).
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.Timestamp start_time = 4;</code>
|
||||
* @return \Rv\Data\Timestamp|null
|
||||
*/
|
||||
public function getStartTime()
|
||||
{
|
||||
return $this->start_time;
|
||||
}
|
||||
|
||||
public function hasStartTime()
|
||||
{
|
||||
return isset($this->start_time);
|
||||
}
|
||||
|
||||
public function clearStartTime()
|
||||
{
|
||||
unset($this->start_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the event starts. Stored as Timestamp seconds (and optional nanos).
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.Timestamp start_time = 4;</code>
|
||||
* @param \Rv\Data\Timestamp $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setStartTime($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Rv\Data\Timestamp::class);
|
||||
$this->start_time = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opaque flags blob observed in samples. Often a single byte (e.g. 0x01)
|
||||
* that ProPresenter uses for recurrence or visibility state.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes flags = 5;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opaque flags blob observed in samples. Often a single byte (e.g. 0x01)
|
||||
* that ProPresenter uses for recurrence or visibility state.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes flags = 5;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setFlags($var)
|
||||
{
|
||||
GPBUtil::checkString($var, False);
|
||||
$this->flags = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the event ends. Optional. Same format as `start_time`.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.Timestamp end_time = 6;</code>
|
||||
* @return \Rv\Data\Timestamp|null
|
||||
*/
|
||||
public function getEndTime()
|
||||
{
|
||||
return $this->end_time;
|
||||
}
|
||||
|
||||
public function hasEndTime()
|
||||
{
|
||||
return isset($this->end_time);
|
||||
}
|
||||
|
||||
public function clearEndTime()
|
||||
{
|
||||
unset($this->end_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the event ends. Optional. Same format as `start_time`.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.Timestamp end_time = 6;</code>
|
||||
* @param \Rv\Data\Timestamp $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setEndTime($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Rv\Data\Timestamp::class);
|
||||
$this->end_time = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that runs when the event fires. Encoded as raw protobuf bytes
|
||||
* (a `rv.data.Action`-shaped message) so the schema can evolve without
|
||||
* breaking existing clients.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes action_data = 8;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getActionData()
|
||||
{
|
||||
return $this->action_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that runs when the event fires. Encoded as raw protobuf bytes
|
||||
* (a `rv.data.Action`-shaped message) so the schema can evolve without
|
||||
* breaking existing clients.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes action_data = 8;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setActionData($var)
|
||||
{
|
||||
GPBUtil::checkString($var, False);
|
||||
$this->action_data = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embedded copy of the macro definition the event triggers. Stored as
|
||||
* raw protobuf bytes (shape compatible with `MacrosDocument.Macro`).
|
||||
*
|
||||
* Generated from protobuf field <code>bytes macro_data = 9;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getMacroData()
|
||||
{
|
||||
return $this->macro_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embedded copy of the macro definition the event triggers. Stored as
|
||||
* raw protobuf bytes (shape compatible with `MacrosDocument.Macro`).
|
||||
*
|
||||
* Generated from protobuf field <code>bytes macro_data = 9;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setMacroData($var)
|
||||
{
|
||||
GPBUtil::checkString($var, False);
|
||||
$this->macro_data = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
115
generated/Rv/Data/KeyMappingsDocument.php
Normal file
115
generated/Rv/Data/KeyMappingsDocument.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: keyMappings.proto
|
||||
|
||||
namespace Rv\Data;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* KeyMappingsDocument is the global ProPresenter `KeyMappings` file. The
|
||||
* reference sample on disk is just an `ApplicationInfo` envelope with an
|
||||
* otherwise empty body — ProPresenter seeds the file at startup. Once the
|
||||
* user binds hot keys we expect them in `mappings`.
|
||||
*
|
||||
* Generated from protobuf message <code>rv.data.KeyMappingsDocument</code>
|
||||
*/
|
||||
class KeyMappingsDocument extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Application metadata of the writer.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.ApplicationInfo application_info = 1;</code>
|
||||
*/
|
||||
protected $application_info = null;
|
||||
/**
|
||||
* Configured key bindings. Empty in the reference sample.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .rv.data.KeyMappingsDocument.Mapping mappings = 2;</code>
|
||||
*/
|
||||
private $mappings;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type \Rv\Data\ApplicationInfo $application_info
|
||||
* Application metadata of the writer.
|
||||
* @type array<\Rv\Data\KeyMappingsDocument\Mapping>|\Google\Protobuf\Internal\RepeatedField $mappings
|
||||
* Configured key bindings. Empty in the reference sample.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\KeyMappings::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Application metadata of the writer.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.ApplicationInfo application_info = 1;</code>
|
||||
* @return \Rv\Data\ApplicationInfo|null
|
||||
*/
|
||||
public function getApplicationInfo()
|
||||
{
|
||||
return $this->application_info;
|
||||
}
|
||||
|
||||
public function hasApplicationInfo()
|
||||
{
|
||||
return isset($this->application_info);
|
||||
}
|
||||
|
||||
public function clearApplicationInfo()
|
||||
{
|
||||
unset($this->application_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Application metadata of the writer.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.ApplicationInfo application_info = 1;</code>
|
||||
* @param \Rv\Data\ApplicationInfo $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setApplicationInfo($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Rv\Data\ApplicationInfo::class);
|
||||
$this->application_info = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configured key bindings. Empty in the reference sample.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .rv.data.KeyMappingsDocument.Mapping mappings = 2;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getMappings()
|
||||
{
|
||||
return $this->mappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configured key bindings. Empty in the reference sample.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .rv.data.KeyMappingsDocument.Mapping mappings = 2;</code>
|
||||
* @param array<\Rv\Data\KeyMappingsDocument\Mapping>|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setMappings($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Rv\Data\KeyMappingsDocument\Mapping::class);
|
||||
$this->mappings = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
196
generated/Rv/Data/KeyMappingsDocument/Mapping.php
Normal file
196
generated/Rv/Data/KeyMappingsDocument/Mapping.php
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: keyMappings.proto
|
||||
|
||||
namespace Rv\Data\KeyMappingsDocument;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Generated from protobuf message <code>rv.data.KeyMappingsDocument.Mapping</code>
|
||||
*/
|
||||
class Mapping extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Optional stable identifier for the mapping.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.UUID uuid = 1;</code>
|
||||
*/
|
||||
protected $uuid = null;
|
||||
/**
|
||||
* The hot key combo that fires the action.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.HotKey hot_key = 2;</code>
|
||||
*/
|
||||
protected $hot_key = null;
|
||||
/**
|
||||
* Action target — typically a macro UUID, a control identifier, or any
|
||||
* other reference ProPresenter chooses to encode. Kept as bytes so we
|
||||
* round-trip cleanly while ProPresenter's internal schema evolves.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes target = 3;</code>
|
||||
*/
|
||||
protected $target = '';
|
||||
/**
|
||||
* Display name (optional).
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 4;</code>
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type \Rv\Data\UUID $uuid
|
||||
* Optional stable identifier for the mapping.
|
||||
* @type \Rv\Data\HotKey $hot_key
|
||||
* The hot key combo that fires the action.
|
||||
* @type string $target
|
||||
* Action target — typically a macro UUID, a control identifier, or any
|
||||
* other reference ProPresenter chooses to encode. Kept as bytes so we
|
||||
* round-trip cleanly while ProPresenter's internal schema evolves.
|
||||
* @type string $name
|
||||
* Display name (optional).
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\KeyMappings::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional stable identifier for the mapping.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.UUID uuid = 1;</code>
|
||||
* @return \Rv\Data\UUID|null
|
||||
*/
|
||||
public function getUuid()
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function hasUuid()
|
||||
{
|
||||
return isset($this->uuid);
|
||||
}
|
||||
|
||||
public function clearUuid()
|
||||
{
|
||||
unset($this->uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional stable identifier for the mapping.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.UUID uuid = 1;</code>
|
||||
* @param \Rv\Data\UUID $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setUuid($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Rv\Data\UUID::class);
|
||||
$this->uuid = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hot key combo that fires the action.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.HotKey hot_key = 2;</code>
|
||||
* @return \Rv\Data\HotKey|null
|
||||
*/
|
||||
public function getHotKey()
|
||||
{
|
||||
return $this->hot_key;
|
||||
}
|
||||
|
||||
public function hasHotKey()
|
||||
{
|
||||
return isset($this->hot_key);
|
||||
}
|
||||
|
||||
public function clearHotKey()
|
||||
{
|
||||
unset($this->hot_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* The hot key combo that fires the action.
|
||||
*
|
||||
* Generated from protobuf field <code>.rv.data.HotKey hot_key = 2;</code>
|
||||
* @param \Rv\Data\HotKey $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setHotKey($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Rv\Data\HotKey::class);
|
||||
$this->hot_key = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action target — typically a macro UUID, a control identifier, or any
|
||||
* other reference ProPresenter chooses to encode. Kept as bytes so we
|
||||
* round-trip cleanly while ProPresenter's internal schema evolves.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes target = 3;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getTarget()
|
||||
{
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action target — typically a macro UUID, a control identifier, or any
|
||||
* other reference ProPresenter chooses to encode. Kept as bytes so we
|
||||
* round-trip cleanly while ProPresenter's internal schema evolves.
|
||||
*
|
||||
* Generated from protobuf field <code>bytes target = 3;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setTarget($var)
|
||||
{
|
||||
GPBUtil::checkString($var, False);
|
||||
$this->target = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name (optional).
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 4;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name (optional).
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 4;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->name = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
51
proto/calendar.proto
Normal file
51
proto/calendar.proto
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package rv.data;
|
||||
|
||||
option cc_enable_arenas = true;
|
||||
option csharp_namespace = "Pro.SerializationInterop.RVProtoData";
|
||||
option swift_prefix = "RVData_";
|
||||
|
||||
import "rvtimestamp.proto";
|
||||
import "uuid.proto";
|
||||
|
||||
// CalendarDocument is the global ProPresenter `Calendar` file. It holds
|
||||
// scheduled events that fire macros at given times. The exact semantics of
|
||||
// the inner sub-messages 8 and 9 (action and embedded macro reference) are
|
||||
// preserved as raw bytes so the document round-trips byte-for-byte; clients
|
||||
// that need to inspect them can decode them with `protoc --decode_raw` or
|
||||
// reach for the raw protobuf message.
|
||||
message CalendarDocument {
|
||||
message Event {
|
||||
// Stable identity of this calendar event.
|
||||
.rv.data.UUID uuid = 1;
|
||||
|
||||
// Display name (e.g. "Doors Open").
|
||||
string name = 2;
|
||||
|
||||
// When the event starts. Stored as Timestamp seconds (and optional nanos).
|
||||
.rv.data.Timestamp start_time = 4;
|
||||
|
||||
// Opaque flags blob observed in samples. Often a single byte (e.g. 0x01)
|
||||
// that ProPresenter uses for recurrence or visibility state.
|
||||
bytes flags = 5;
|
||||
|
||||
// When the event ends. Optional. Same format as `start_time`.
|
||||
.rv.data.Timestamp end_time = 6;
|
||||
|
||||
// Action that runs when the event fires. Encoded as raw protobuf bytes
|
||||
// (a `rv.data.Action`-shaped message) so the schema can evolve without
|
||||
// breaking existing clients.
|
||||
bytes action_data = 8;
|
||||
|
||||
// Embedded copy of the macro definition the event triggers. Stored as
|
||||
// raw protobuf bytes (shape compatible with `MacrosDocument.Macro`).
|
||||
bytes macro_data = 9;
|
||||
}
|
||||
|
||||
// Events scheduled in the calendar, in the order ProPresenter wrote them.
|
||||
repeated Event events = 1;
|
||||
|
||||
// Source / mode flag observed in samples (value `1`). Treated as opaque.
|
||||
uint32 mode = 2;
|
||||
}
|
||||
39
proto/keyMappings.proto
Normal file
39
proto/keyMappings.proto
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package rv.data;
|
||||
|
||||
option cc_enable_arenas = true;
|
||||
option csharp_namespace = "Pro.SerializationInterop.RVProtoData";
|
||||
option swift_prefix = "RVData_";
|
||||
|
||||
import "applicationInfo.proto";
|
||||
import "hotKey.proto";
|
||||
import "uuid.proto";
|
||||
|
||||
// KeyMappingsDocument is the global ProPresenter `KeyMappings` file. The
|
||||
// reference sample on disk is just an `ApplicationInfo` envelope with an
|
||||
// otherwise empty body — ProPresenter seeds the file at startup. Once the
|
||||
// user binds hot keys we expect them in `mappings`.
|
||||
message KeyMappingsDocument {
|
||||
message Mapping {
|
||||
// Optional stable identifier for the mapping.
|
||||
.rv.data.UUID uuid = 1;
|
||||
|
||||
// The hot key combo that fires the action.
|
||||
.rv.data.HotKey hot_key = 2;
|
||||
|
||||
// Action target — typically a macro UUID, a control identifier, or any
|
||||
// other reference ProPresenter chooses to encode. Kept as bytes so we
|
||||
// round-trip cleanly while ProPresenter's internal schema evolves.
|
||||
bytes target = 3;
|
||||
|
||||
// Display name (optional).
|
||||
string name = 4;
|
||||
}
|
||||
|
||||
// Application metadata of the writer.
|
||||
.rv.data.ApplicationInfo application_info = 1;
|
||||
|
||||
// Configured key bindings. Empty in the reference sample.
|
||||
repeated Mapping mappings = 2;
|
||||
}
|
||||
38
src/CCLIFileReader.php
Normal file
38
src/CCLIFileReader.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Rv\Data\CCLIDocument;
|
||||
|
||||
final class CCLIFileReader
|
||||
{
|
||||
public static function read(string $filePath): CCLILibrary
|
||||
{
|
||||
if ($filePath === '' || !is_file($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('CCLI file not found: %s', $filePath));
|
||||
}
|
||||
|
||||
$size = filesize($filePath);
|
||||
if ($size === false) {
|
||||
throw new RuntimeException(sprintf('Unable to determine file size: %s', $filePath));
|
||||
}
|
||||
|
||||
if ($size === 0) {
|
||||
throw new RuntimeException(sprintf('CCLI file is empty: %s', $filePath));
|
||||
}
|
||||
|
||||
$data = file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException(sprintf('Unable to read CCLI file: %s', $filePath));
|
||||
}
|
||||
|
||||
$document = new CCLIDocument();
|
||||
$document->mergeFromString($data);
|
||||
|
||||
return new CCLILibrary($document);
|
||||
}
|
||||
}
|
||||
26
src/CCLIFileWriter.php
Normal file
26
src/CCLIFileWriter.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class CCLIFileWriter
|
||||
{
|
||||
public static function write(CCLILibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$data = $library->getDocument()->serializeToString();
|
||||
$writtenBytes = file_put_contents($filePath, $data);
|
||||
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write CCLI file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/CCLILibrary.php
Normal file
103
src/CCLILibrary.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\ApplicationInfo;
|
||||
use Rv\Data\CCLIDocument;
|
||||
use Rv\Data\Template\Slide;
|
||||
|
||||
class CCLILibrary
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CCLIDocument $document,
|
||||
) {
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function isCCLIDisplayEnabled(): bool
|
||||
{
|
||||
return $this->document->getEnableCcliDisplay();
|
||||
}
|
||||
|
||||
public function setCCLIDisplayEnabled(bool $enabled): self
|
||||
{
|
||||
$this->document->setEnableCcliDisplay($enabled);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCCLILicense(): string
|
||||
{
|
||||
return $this->document->getCcliLicense();
|
||||
}
|
||||
|
||||
public function setCCLILicense(string $license): self
|
||||
{
|
||||
$this->document->setCcliLicense($license);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDisplayType(): int
|
||||
{
|
||||
return $this->document->getDisplayType();
|
||||
}
|
||||
|
||||
public function setDisplayType(int $displayType): self
|
||||
{
|
||||
$this->document->setDisplayType($displayType);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTemplate(): ?Slide
|
||||
{
|
||||
if (!$this->document->hasTemplate()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->document->getTemplate();
|
||||
}
|
||||
|
||||
public function setTemplate(?Slide $template): self
|
||||
{
|
||||
if ($template === null) {
|
||||
$this->document->clearTemplate();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->document->setTemplate($template);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationInfo(): ?ApplicationInfo
|
||||
{
|
||||
return $this->document->getApplicationInfo();
|
||||
}
|
||||
|
||||
public function setApplicationInfo(?ApplicationInfo $applicationInfo): self
|
||||
{
|
||||
if ($applicationInfo === null) {
|
||||
$this->document->clearApplicationInfo();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->document->setApplicationInfo($applicationInfo);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDocument(): CCLIDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
}
|
||||
140
src/CalendarEvent.php
Normal file
140
src/CalendarEvent.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\CalendarDocument\Event as EventProto;
|
||||
use Rv\Data\Timestamp;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class CalendarEvent
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EventProto $event,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->event->getUuid()?->getString() ?? '';
|
||||
}
|
||||
|
||||
public function setUuid(string $uuid): self
|
||||
{
|
||||
$proto = new UUID();
|
||||
$proto->setString($uuid);
|
||||
$this->event->setUuid($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->event->getName();
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->event->setName($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStartTime(): ?Timestamp
|
||||
{
|
||||
return $this->event->getStartTime();
|
||||
}
|
||||
|
||||
public function setStartTime(Timestamp $timestamp): self
|
||||
{
|
||||
$this->event->setStartTime($timestamp);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStartTimeSeconds(): ?int
|
||||
{
|
||||
$seconds = $this->event->getStartTime()?->getSeconds();
|
||||
|
||||
return $seconds === null ? null : (int) $seconds;
|
||||
}
|
||||
|
||||
public function setStartTimeSeconds(int $seconds): self
|
||||
{
|
||||
$timestamp = new Timestamp();
|
||||
$timestamp->setSeconds($seconds);
|
||||
$this->event->setStartTime($timestamp);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEndTime(): ?Timestamp
|
||||
{
|
||||
return $this->event->getEndTime();
|
||||
}
|
||||
|
||||
public function setEndTime(Timestamp $timestamp): self
|
||||
{
|
||||
$this->event->setEndTime($timestamp);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEndTimeSeconds(): ?int
|
||||
{
|
||||
$seconds = $this->event->getEndTime()?->getSeconds();
|
||||
|
||||
return $seconds === null ? null : (int) $seconds;
|
||||
}
|
||||
|
||||
public function setEndTimeSeconds(int $seconds): self
|
||||
{
|
||||
$timestamp = new Timestamp();
|
||||
$timestamp->setSeconds($seconds);
|
||||
$this->event->setEndTime($timestamp);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFlags(): string
|
||||
{
|
||||
return $this->event->getFlags();
|
||||
}
|
||||
|
||||
public function setFlags(string $flags): self
|
||||
{
|
||||
$this->event->setFlags($flags);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getActionData(): string
|
||||
{
|
||||
return $this->event->getActionData();
|
||||
}
|
||||
|
||||
public function setActionData(string $actionData): self
|
||||
{
|
||||
$this->event->setActionData($actionData);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMacroData(): string
|
||||
{
|
||||
return $this->event->getMacroData();
|
||||
}
|
||||
|
||||
public function setMacroData(string $macroData): self
|
||||
{
|
||||
$this->event->setMacroData($macroData);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProto(): EventProto
|
||||
{
|
||||
return $this->event;
|
||||
}
|
||||
}
|
||||
37
src/CalendarFileReader.php
Normal file
37
src/CalendarFileReader.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Rv\Data\CalendarDocument;
|
||||
|
||||
final class CalendarFileReader
|
||||
{
|
||||
public static function read(string $filePath): CalendarLibrary
|
||||
{
|
||||
if ($filePath === '' || !is_file($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('Calendar file not found: %s', $filePath));
|
||||
}
|
||||
|
||||
$size = filesize($filePath);
|
||||
if ($size === false) {
|
||||
throw new RuntimeException(sprintf('Unable to determine file size: %s', $filePath));
|
||||
}
|
||||
if ($size === 0) {
|
||||
throw new RuntimeException(sprintf('Calendar file is empty: %s', $filePath));
|
||||
}
|
||||
|
||||
$data = file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException(sprintf('Unable to read Calendar file: %s', $filePath));
|
||||
}
|
||||
|
||||
$document = new CalendarDocument();
|
||||
$document->mergeFromString($data);
|
||||
|
||||
return new CalendarLibrary($document);
|
||||
}
|
||||
}
|
||||
24
src/CalendarFileWriter.php
Normal file
24
src/CalendarFileWriter.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class CalendarFileWriter
|
||||
{
|
||||
public static function write(CalendarLibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$writtenBytes = file_put_contents($filePath, $library->getDocument()->serializeToString());
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write Calendar file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/CalendarLibrary.php
Normal file
127
src/CalendarLibrary.php
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\CalendarDocument;
|
||||
use Rv\Data\CalendarDocument\Event as EventProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class CalendarLibrary
|
||||
{
|
||||
/** @var CalendarEvent[] */
|
||||
private array $events = [];
|
||||
|
||||
/** @var array<string, CalendarEvent> */
|
||||
private array $eventsByUuid = [];
|
||||
|
||||
/** @var array<string, CalendarEvent> */
|
||||
private array $eventsByName = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly CalendarDocument $document,
|
||||
) {
|
||||
$this->rebuildIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CalendarEvent[]
|
||||
*/
|
||||
public function getEvents(): array
|
||||
{
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->events);
|
||||
}
|
||||
|
||||
public function getEventByUuid(string $uuid): ?CalendarEvent
|
||||
{
|
||||
return $this->eventsByUuid[strtoupper($uuid)] ?? null;
|
||||
}
|
||||
|
||||
public function getEventByName(string $name): ?CalendarEvent
|
||||
{
|
||||
return $this->eventsByName[$name] ?? null;
|
||||
}
|
||||
|
||||
public function addEvent(string $name, string $uuid): CalendarEvent
|
||||
{
|
||||
$proto = new EventProto();
|
||||
$uuidProto = new UUID();
|
||||
$uuidProto->setString($uuid);
|
||||
$proto->setUuid($uuidProto);
|
||||
$proto->setName($name);
|
||||
|
||||
$existing = iterator_to_array($this->document->getEvents());
|
||||
$existing[] = $proto;
|
||||
$this->document->setEvents($existing);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return $this->getEventByUuid($uuid) ?? new CalendarEvent($proto);
|
||||
}
|
||||
|
||||
public function removeEvent(string $uuid): bool
|
||||
{
|
||||
$needle = strtoupper($uuid);
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($this->document->getEvents() as $proto) {
|
||||
$current = strtoupper($proto->getUuid()?->getString() ?? '');
|
||||
if (!$removed && $current === $needle) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $proto;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->document->setEvents($kept);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getMode(): int
|
||||
{
|
||||
return $this->document->getMode();
|
||||
}
|
||||
|
||||
public function setMode(int $mode): void
|
||||
{
|
||||
$this->document->setMode($mode);
|
||||
}
|
||||
|
||||
public function getDocument(): CalendarDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
private function rebuildIndex(): void
|
||||
{
|
||||
$this->events = [];
|
||||
$this->eventsByUuid = [];
|
||||
$this->eventsByName = [];
|
||||
|
||||
foreach ($this->document->getEvents() as $proto) {
|
||||
$event = new CalendarEvent($proto);
|
||||
$this->events[] = $event;
|
||||
|
||||
$uuid = strtoupper($event->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->eventsByUuid[$uuid] = $event;
|
||||
}
|
||||
|
||||
$name = $event->getName();
|
||||
if ($name !== '') {
|
||||
$this->eventsByName[$name] ??= $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
199
src/ClearGroupDefinition.php
Normal file
199
src/ClearGroupDefinition.php
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\ClearGroupsDocument\ClearGroup as ClearGroupProto;
|
||||
use Rv\Data\Color;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class ClearGroupDefinition
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClearGroupProto $group,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->group->getUuid()?->getString() ?? '';
|
||||
}
|
||||
|
||||
public function setUuid(string $uuid): self
|
||||
{
|
||||
$proto = new UUID();
|
||||
$proto->setString($uuid);
|
||||
$this->group->setUuid($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->group->getName();
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->group->setName($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getLayerTargets(): array
|
||||
{
|
||||
return iterator_to_array($this->group->getLayerTargets());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $targets
|
||||
*/
|
||||
public function setLayerTargets(array $targets): self
|
||||
{
|
||||
$this->group->setLayerTargets($targets);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isHiddenInPreview(): bool
|
||||
{
|
||||
return $this->group->getIsHiddenInPreview();
|
||||
}
|
||||
|
||||
public function setHiddenInPreview(bool $hidden): self
|
||||
{
|
||||
$this->group->setIsHiddenInPreview($hidden);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImageData(): string
|
||||
{
|
||||
return $this->group->getImageData();
|
||||
}
|
||||
|
||||
public function setImageData(string $imageData): self
|
||||
{
|
||||
$this->group->setImageData($imageData);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImageType(): int
|
||||
{
|
||||
return $this->group->getImageType();
|
||||
}
|
||||
|
||||
public function setImageType(int $imageType): self
|
||||
{
|
||||
$this->group->setImageType($imageType);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isIconTinted(): bool
|
||||
{
|
||||
return $this->group->getIsIconTinted();
|
||||
}
|
||||
|
||||
public function setIconTinted(bool $tinted): self
|
||||
{
|
||||
$this->group->setIsIconTinted($tinted);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{r: float, g: float, b: float, a: float}|null
|
||||
*/
|
||||
public function getColor(): ?array
|
||||
{
|
||||
if (!$this->group->hasIconTintColor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$color = $this->group->getIconTintColor();
|
||||
|
||||
return [
|
||||
'r' => $color->getRed(),
|
||||
'g' => $color->getGreen(),
|
||||
'b' => $color->getBlue(),
|
||||
'a' => $color->getAlpha(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getColorHex(): ?string
|
||||
{
|
||||
$color = $this->getColor();
|
||||
if ($color === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'#%02X%02X%02X',
|
||||
(int) round(max(0.0, min(1.0, $color['r'])) * 255),
|
||||
(int) round(max(0.0, min(1.0, $color['g'])) * 255),
|
||||
(int) round(max(0.0, min(1.0, $color['b'])) * 255),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{r: float, g: float, b: float, a?: float}|null $color
|
||||
*/
|
||||
public function setColor(?array $color): self
|
||||
{
|
||||
if ($color === null) {
|
||||
$this->group->clearIconTintColor();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$proto = new Color();
|
||||
$proto->setRed((float) $color['r']);
|
||||
$proto->setGreen((float) $color['g']);
|
||||
$proto->setBlue((float) $color['b']);
|
||||
$proto->setAlpha((float) ($color['a'] ?? 1.0));
|
||||
$this->group->setIconTintColor($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getTimelineTargets(): array
|
||||
{
|
||||
return iterator_to_array($this->group->getTimelineTargets());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $targets
|
||||
*/
|
||||
public function setTimelineTargets(array $targets): self
|
||||
{
|
||||
$this->group->setTimelineTargets($targets);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clearsPresentationNextSlide(): bool
|
||||
{
|
||||
return $this->group->getClearPresentationNextSlide();
|
||||
}
|
||||
|
||||
public function setClearPresentationNextSlide(bool $clear): self
|
||||
{
|
||||
$this->group->setClearPresentationNextSlide($clear);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProto(): ClearGroupProto
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
}
|
||||
38
src/ClearGroupsFileReader.php
Normal file
38
src/ClearGroupsFileReader.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Rv\Data\ClearGroupsDocument;
|
||||
|
||||
final class ClearGroupsFileReader
|
||||
{
|
||||
public static function read(string $filePath): ClearGroupsLibrary
|
||||
{
|
||||
if ($filePath === '' || !is_file($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('ClearGroups file not found: %s', $filePath));
|
||||
}
|
||||
|
||||
$size = filesize($filePath);
|
||||
if ($size === false) {
|
||||
throw new RuntimeException(sprintf('Unable to determine file size: %s', $filePath));
|
||||
}
|
||||
|
||||
if ($size === 0) {
|
||||
throw new RuntimeException(sprintf('ClearGroups file is empty: %s', $filePath));
|
||||
}
|
||||
|
||||
$data = file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException(sprintf('Unable to read ClearGroups file: %s', $filePath));
|
||||
}
|
||||
|
||||
$document = new ClearGroupsDocument();
|
||||
$document->mergeFromString($data);
|
||||
|
||||
return new ClearGroupsLibrary($document);
|
||||
}
|
||||
}
|
||||
26
src/ClearGroupsFileWriter.php
Normal file
26
src/ClearGroupsFileWriter.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class ClearGroupsFileWriter
|
||||
{
|
||||
public static function write(ClearGroupsLibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$data = $library->getDocument()->serializeToString();
|
||||
$writtenBytes = file_put_contents($filePath, $data);
|
||||
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write ClearGroups file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
119
src/ClearGroupsLibrary.php
Normal file
119
src/ClearGroupsLibrary.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\ClearGroupsDocument;
|
||||
use Rv\Data\ClearGroupsDocument\ClearGroup as ClearGroupProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class ClearGroupsLibrary
|
||||
{
|
||||
/** @var ClearGroupDefinition[] */
|
||||
private array $groups = [];
|
||||
|
||||
/** @var array<string, ClearGroupDefinition> */
|
||||
private array $groupsByUuid = [];
|
||||
|
||||
/** @var array<string, ClearGroupDefinition> */
|
||||
private array $groupsByName = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly ClearGroupsDocument $document,
|
||||
) {
|
||||
$this->rebuildIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return clear groups in document order.
|
||||
*
|
||||
* @return ClearGroupDefinition[]
|
||||
*/
|
||||
public function getGroups(): array
|
||||
{
|
||||
return $this->groups;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->groups);
|
||||
}
|
||||
|
||||
public function getClearGroupByUuid(string $uuid): ?ClearGroupDefinition
|
||||
{
|
||||
return $this->groupsByUuid[strtoupper($uuid)] ?? null;
|
||||
}
|
||||
|
||||
public function getClearGroupByName(string $name): ?ClearGroupDefinition
|
||||
{
|
||||
return $this->groupsByName[$name] ?? null;
|
||||
}
|
||||
|
||||
public function addClearGroup(string $name, string $uuid): ClearGroupDefinition
|
||||
{
|
||||
$proto = new ClearGroupProto();
|
||||
$uuidProto = new UUID();
|
||||
$uuidProto->setString($uuid);
|
||||
$proto->setUuid($uuidProto);
|
||||
$proto->setName($name);
|
||||
|
||||
$existing = iterator_to_array($this->document->getGroups());
|
||||
$existing[] = $proto;
|
||||
$this->document->setGroups($existing);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return $this->getClearGroupByUuid($uuid) ?? new ClearGroupDefinition($proto);
|
||||
}
|
||||
|
||||
public function removeClearGroup(string $uuid): bool
|
||||
{
|
||||
$needle = strtoupper($uuid);
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($this->document->getGroups() as $proto) {
|
||||
$current = strtoupper($proto->getUuid()?->getString() ?? '');
|
||||
if (!$removed && $current === $needle) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $proto;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->document->setGroups($kept);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDocument(): ClearGroupsDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
private function rebuildIndex(): void
|
||||
{
|
||||
$this->groups = [];
|
||||
$this->groupsByUuid = [];
|
||||
$this->groupsByName = [];
|
||||
|
||||
foreach ($this->document->getGroups() as $proto) {
|
||||
$group = new ClearGroupDefinition($proto);
|
||||
$this->groups[] = $group;
|
||||
|
||||
$uuid = strtoupper($group->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->groupsByUuid[$uuid] = $group;
|
||||
}
|
||||
|
||||
$name = $group->getName();
|
||||
if ($name !== '') {
|
||||
$this->groupsByName[$name] ??= $group;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/CommunicationDevice.php
Normal file
75
src/CommunicationDevice.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
class CommunicationDevice
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $fields;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $fields
|
||||
*/
|
||||
public function __construct(?array $fields = null)
|
||||
{
|
||||
$this->fields = $fields ?? [];
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return (string) ($this->fields['id'] ?? '');
|
||||
}
|
||||
|
||||
public function setId(string $id): self
|
||||
{
|
||||
$this->fields['id'] = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return (string) ($this->fields['name'] ?? '');
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->fields['name'] = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return (string) ($this->fields['type'] ?? '');
|
||||
}
|
||||
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->fields['type'] = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAddress(): string
|
||||
{
|
||||
return (string) ($this->fields['address'] ?? '');
|
||||
}
|
||||
|
||||
public function setAddress(string $address): self
|
||||
{
|
||||
$this->fields['address'] = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
}
|
||||
25
src/CommunicationDevicesFileReader.php
Normal file
25
src/CommunicationDevicesFileReader.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class CommunicationDevicesFileReader
|
||||
{
|
||||
public static function read(string $filePath): CommunicationDevicesLibrary
|
||||
{
|
||||
if ($filePath === '' || !is_file($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('CommunicationDevices file not found: %s', $filePath));
|
||||
}
|
||||
|
||||
$contents = file_get_contents($filePath);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException(sprintf('Unable to read CommunicationDevices file: %s', $filePath));
|
||||
}
|
||||
|
||||
return CommunicationDevicesLibrary::fromJson($contents);
|
||||
}
|
||||
}
|
||||
25
src/CommunicationDevicesFileWriter.php
Normal file
25
src/CommunicationDevicesFileWriter.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class CommunicationDevicesFileWriter
|
||||
{
|
||||
public static function write(CommunicationDevicesLibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$json = $library->toJson(JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
$writtenBytes = file_put_contents($filePath, $json);
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write CommunicationDevices file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/CommunicationDevicesLibrary.php
Normal file
94
src/CommunicationDevicesLibrary.php
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class CommunicationDevicesLibrary
|
||||
{
|
||||
/** @var CommunicationDevice[] */
|
||||
private array $devices = [];
|
||||
|
||||
/**
|
||||
* @param CommunicationDevice[] $devices
|
||||
*/
|
||||
public function __construct(array $devices = [])
|
||||
{
|
||||
$this->devices = array_values($devices);
|
||||
}
|
||||
|
||||
public static function fromJson(string $json): self
|
||||
{
|
||||
$decoded = json_decode($json, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new RuntimeException('Unable to decode CommunicationDevices JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
if (!is_array($decoded)) {
|
||||
throw new RuntimeException('CommunicationDevices JSON must decode to an array.');
|
||||
}
|
||||
|
||||
$devices = [];
|
||||
foreach ($decoded as $entry) {
|
||||
if (is_array($entry)) {
|
||||
/** @var array<string, mixed> $entry */
|
||||
$devices[] = new CommunicationDevice($entry);
|
||||
}
|
||||
}
|
||||
|
||||
return new self($devices);
|
||||
}
|
||||
|
||||
public function toJson(int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES): string
|
||||
{
|
||||
$json = json_encode($this->getDocument(), $flags);
|
||||
if ($json === false) {
|
||||
throw new RuntimeException('Unable to encode CommunicationDevices JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getDocument(): array
|
||||
{
|
||||
return array_map(static fn (CommunicationDevice $device): array => $device->toArray(), $this->devices);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CommunicationDevice[]
|
||||
*/
|
||||
public function getDevices(): array
|
||||
{
|
||||
return $this->devices;
|
||||
}
|
||||
|
||||
public function addDevice(CommunicationDevice $device): CommunicationDevice
|
||||
{
|
||||
$this->devices[] = $device;
|
||||
|
||||
return $device;
|
||||
}
|
||||
|
||||
public function removeDevice(string $id): bool
|
||||
{
|
||||
foreach ($this->devices as $index => $device) {
|
||||
if ($device->getId() === $id) {
|
||||
array_splice($this->devices, $index, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->devices);
|
||||
}
|
||||
}
|
||||
119
src/GroupDefinition.php
Normal file
119
src/GroupDefinition.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\Color;
|
||||
use Rv\Data\Group as GroupProto;
|
||||
use Rv\Data\HotKey;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class GroupDefinition
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GroupProto $group,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->group->getUuid()?->getString() ?? '';
|
||||
}
|
||||
|
||||
public function setUuid(string $uuid): self
|
||||
{
|
||||
$proto = new UUID();
|
||||
$proto->setString($uuid);
|
||||
$this->group->setUuid($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->group->getName();
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->group->setName($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{r: float, g: float, b: float, a: float}|null
|
||||
*/
|
||||
public function getColor(): ?array
|
||||
{
|
||||
if (!$this->group->hasColor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$color = $this->group->getColor();
|
||||
|
||||
return [
|
||||
'r' => $color->getRed(),
|
||||
'g' => $color->getGreen(),
|
||||
'b' => $color->getBlue(),
|
||||
'a' => $color->getAlpha(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getColorHex(): ?string
|
||||
{
|
||||
$color = $this->getColor();
|
||||
if ($color === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'#%02X%02X%02X',
|
||||
(int) round(max(0.0, min(1.0, $color['r'])) * 255),
|
||||
(int) round(max(0.0, min(1.0, $color['g'])) * 255),
|
||||
(int) round(max(0.0, min(1.0, $color['b'])) * 255),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{r: float, g: float, b: float, a?: float}|null $color
|
||||
*/
|
||||
public function setColor(?array $color): self
|
||||
{
|
||||
if ($color === null) {
|
||||
$this->group->clearColor();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$proto = new Color();
|
||||
$proto->setRed((float) $color['r']);
|
||||
$proto->setGreen((float) $color['g']);
|
||||
$proto->setBlue((float) $color['b']);
|
||||
$proto->setAlpha((float) ($color['a'] ?? 1.0));
|
||||
$this->group->setColor($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHotKey(): ?HotKey
|
||||
{
|
||||
return $this->group->getHotKey();
|
||||
}
|
||||
|
||||
public function getApplicationGroupName(): string
|
||||
{
|
||||
return $this->group->getApplicationGroupName();
|
||||
}
|
||||
|
||||
public function getApplicationGroupUuid(): string
|
||||
{
|
||||
return $this->group->getApplicationGroupIdentifier()?->getString() ?? '';
|
||||
}
|
||||
|
||||
public function getProto(): GroupProto
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
}
|
||||
117
src/GroupLibrary.php
Normal file
117
src/GroupLibrary.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\Group as GroupProto;
|
||||
use Rv\Data\ProGroupsDocument;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class GroupLibrary
|
||||
{
|
||||
/** @var GroupDefinition[] */
|
||||
private array $groups = [];
|
||||
|
||||
/** @var array<string, GroupDefinition> */
|
||||
private array $groupsByUuid = [];
|
||||
|
||||
/** @var array<string, GroupDefinition> */
|
||||
private array $groupsByName = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly ProGroupsDocument $document,
|
||||
) {
|
||||
$this->rebuildIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GroupDefinition[]
|
||||
*/
|
||||
public function getGroups(): array
|
||||
{
|
||||
return $this->groups;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->groups);
|
||||
}
|
||||
|
||||
public function getGroupByUuid(string $uuid): ?GroupDefinition
|
||||
{
|
||||
return $this->groupsByUuid[strtoupper($uuid)] ?? null;
|
||||
}
|
||||
|
||||
public function getGroupByName(string $name): ?GroupDefinition
|
||||
{
|
||||
return $this->groupsByName[$name] ?? null;
|
||||
}
|
||||
|
||||
public function addGroup(string $name, string $uuid): GroupDefinition
|
||||
{
|
||||
$proto = new GroupProto();
|
||||
$uuidProto = new UUID();
|
||||
$uuidProto->setString($uuid);
|
||||
$proto->setUuid($uuidProto);
|
||||
$proto->setName($name);
|
||||
|
||||
$existing = iterator_to_array($this->document->getGroups());
|
||||
$existing[] = $proto;
|
||||
$this->document->setGroups($existing);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return $this->getGroupByUuid($uuid) ?? new GroupDefinition($proto);
|
||||
}
|
||||
|
||||
public function removeGroup(string $uuid): bool
|
||||
{
|
||||
$needle = strtoupper($uuid);
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($this->document->getGroups() as $proto) {
|
||||
$current = strtoupper($proto->getUuid()?->getString() ?? '');
|
||||
if (!$removed && $current === $needle) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $proto;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->document->setGroups($kept);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDocument(): ProGroupsDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
private function rebuildIndex(): void
|
||||
{
|
||||
$this->groups = [];
|
||||
$this->groupsByUuid = [];
|
||||
$this->groupsByName = [];
|
||||
|
||||
foreach ($this->document->getGroups() as $proto) {
|
||||
$group = new GroupDefinition($proto);
|
||||
$this->groups[] = $group;
|
||||
|
||||
$uuid = strtoupper($group->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->groupsByUuid[$uuid] = $group;
|
||||
}
|
||||
|
||||
$name = $group->getName();
|
||||
if ($name !== '') {
|
||||
$this->groupsByName[$name] ??= $group;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/GroupsFileReader.php
Normal file
38
src/GroupsFileReader.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Rv\Data\ProGroupsDocument;
|
||||
|
||||
final class GroupsFileReader
|
||||
{
|
||||
public static function read(string $filePath): GroupLibrary
|
||||
{
|
||||
if ($filePath === '' || !is_file($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('Groups file not found: %s', $filePath));
|
||||
}
|
||||
|
||||
$size = filesize($filePath);
|
||||
if ($size === false) {
|
||||
throw new RuntimeException(sprintf('Unable to determine file size: %s', $filePath));
|
||||
}
|
||||
|
||||
if ($size === 0) {
|
||||
throw new RuntimeException(sprintf('Groups file is empty: %s', $filePath));
|
||||
}
|
||||
|
||||
$data = file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException(sprintf('Unable to read Groups file: %s', $filePath));
|
||||
}
|
||||
|
||||
$document = new ProGroupsDocument();
|
||||
$document->mergeFromString($data);
|
||||
|
||||
return new GroupLibrary($document);
|
||||
}
|
||||
}
|
||||
26
src/GroupsFileWriter.php
Normal file
26
src/GroupsFileWriter.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class GroupsFileWriter
|
||||
{
|
||||
public static function write(GroupLibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$data = $library->getDocument()->serializeToString();
|
||||
$writtenBytes = file_put_contents($filePath, $data);
|
||||
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write Groups file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/KeyMapping.php
Normal file
82
src/KeyMapping.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\HotKey;
|
||||
use Rv\Data\KeyMappingsDocument\Mapping as MappingProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class KeyMapping
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MappingProto $mapping,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->mapping->getUuid()?->getString() ?? '';
|
||||
}
|
||||
|
||||
public function setUuid(string $uuid): self
|
||||
{
|
||||
$proto = new UUID();
|
||||
$proto->setString($uuid);
|
||||
$this->mapping->setUuid($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->mapping->getName();
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->mapping->setName($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHotKey(): ?HotKey
|
||||
{
|
||||
if (!$this->mapping->hasHotKey()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->mapping->getHotKey();
|
||||
}
|
||||
|
||||
public function setHotKey(?HotKey $hotKey): self
|
||||
{
|
||||
if ($hotKey === null) {
|
||||
$this->mapping->clearHotKey();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->mapping->setHotKey($hotKey);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTarget(): string
|
||||
{
|
||||
return $this->mapping->getTarget();
|
||||
}
|
||||
|
||||
public function setTarget(string $target): self
|
||||
{
|
||||
$this->mapping->setTarget($target);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProto(): MappingProto
|
||||
{
|
||||
return $this->mapping;
|
||||
}
|
||||
}
|
||||
38
src/KeyMappingsFileReader.php
Normal file
38
src/KeyMappingsFileReader.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Rv\Data\KeyMappingsDocument;
|
||||
|
||||
final class KeyMappingsFileReader
|
||||
{
|
||||
public static function read(string $filePath): KeyMappingsLibrary
|
||||
{
|
||||
if ($filePath === '' || !is_file($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('KeyMappings file not found: %s', $filePath));
|
||||
}
|
||||
|
||||
$size = filesize($filePath);
|
||||
if ($size === false) {
|
||||
throw new RuntimeException(sprintf('Unable to determine file size: %s', $filePath));
|
||||
}
|
||||
|
||||
if ($size === 0) {
|
||||
throw new RuntimeException(sprintf('KeyMappings file is empty: %s', $filePath));
|
||||
}
|
||||
|
||||
$data = file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException(sprintf('Unable to read KeyMappings file: %s', $filePath));
|
||||
}
|
||||
|
||||
$document = new KeyMappingsDocument();
|
||||
$document->mergeFromString($data);
|
||||
|
||||
return new KeyMappingsLibrary($document);
|
||||
}
|
||||
}
|
||||
26
src/KeyMappingsFileWriter.php
Normal file
26
src/KeyMappingsFileWriter.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class KeyMappingsFileWriter
|
||||
{
|
||||
public static function write(KeyMappingsLibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$data = $library->getDocument()->serializeToString();
|
||||
$writtenBytes = file_put_contents($filePath, $data);
|
||||
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write KeyMappings file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/KeyMappingsLibrary.php
Normal file
139
src/KeyMappingsLibrary.php
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\ApplicationInfo;
|
||||
use Rv\Data\KeyMappingsDocument;
|
||||
use Rv\Data\KeyMappingsDocument\Mapping as MappingProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class KeyMappingsLibrary
|
||||
{
|
||||
/** @var KeyMapping[] */
|
||||
private array $mappings = [];
|
||||
|
||||
/** @var array<string, KeyMapping> */
|
||||
private array $mappingsByUuid = [];
|
||||
|
||||
/** @var array<string, KeyMapping> */
|
||||
private array $mappingsByName = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly KeyMappingsDocument $document,
|
||||
) {
|
||||
$this->rebuildIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return key mappings in document order.
|
||||
*
|
||||
* @return KeyMapping[]
|
||||
*/
|
||||
public function getMappings(): array
|
||||
{
|
||||
return $this->mappings;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->mappings);
|
||||
}
|
||||
|
||||
public function getMappingByUuid(string $uuid): ?KeyMapping
|
||||
{
|
||||
return $this->mappingsByUuid[strtoupper($uuid)] ?? null;
|
||||
}
|
||||
|
||||
public function getMappingByName(string $name): ?KeyMapping
|
||||
{
|
||||
return $this->mappingsByName[$name] ?? null;
|
||||
}
|
||||
|
||||
public function addMapping(string $name, string $uuid, string $target = ''): KeyMapping
|
||||
{
|
||||
$proto = new MappingProto();
|
||||
$uuidProto = new UUID();
|
||||
$uuidProto->setString($uuid);
|
||||
$proto->setUuid($uuidProto);
|
||||
$proto->setName($name);
|
||||
$proto->setTarget($target);
|
||||
|
||||
$existing = iterator_to_array($this->document->getMappings());
|
||||
$existing[] = $proto;
|
||||
$this->document->setMappings($existing);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return $this->getMappingByUuid($uuid) ?? new KeyMapping($proto);
|
||||
}
|
||||
|
||||
public function removeMapping(string $uuid): bool
|
||||
{
|
||||
$needle = strtoupper($uuid);
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($this->document->getMappings() as $proto) {
|
||||
$current = strtoupper($proto->getUuid()?->getString() ?? '');
|
||||
if (!$removed && $current === $needle) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $proto;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->document->setMappings($kept);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getApplicationInfo(): ?ApplicationInfo
|
||||
{
|
||||
return $this->document->getApplicationInfo();
|
||||
}
|
||||
|
||||
public function setApplicationInfo(?ApplicationInfo $applicationInfo): self
|
||||
{
|
||||
if ($applicationInfo === null) {
|
||||
$this->document->clearApplicationInfo();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->document->setApplicationInfo($applicationInfo);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDocument(): KeyMappingsDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
private function rebuildIndex(): void
|
||||
{
|
||||
$this->mappings = [];
|
||||
$this->mappingsByUuid = [];
|
||||
$this->mappingsByName = [];
|
||||
|
||||
foreach ($this->document->getMappings() as $proto) {
|
||||
$mapping = new KeyMapping($proto);
|
||||
$this->mappings[] = $mapping;
|
||||
|
||||
$uuid = strtoupper($mapping->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->mappingsByUuid[$uuid] = $mapping;
|
||||
}
|
||||
|
||||
$name = $mapping->getName();
|
||||
if ($name !== '') {
|
||||
$this->mappingsByName[$name] ??= $mapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Rv\Data\Action\Label as LabelProto;
|
||||
use Rv\Data\Color;
|
||||
|
||||
/**
|
||||
* Wraps a protobuf {@see LabelProto} from the global ProPresenter `Labels`
|
||||
* file. Surfaces the label's display name (the protobuf `text` field) and an
|
||||
* optional color used by the UI to tint slides / cues that carry the label.
|
||||
*
|
||||
* Labels do not have a UUID — identity is the name string.
|
||||
*/
|
||||
class Label
|
||||
{
|
||||
public function __construct(
|
||||
|
|
@ -20,30 +15,24 @@ class Label
|
|||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name (the protobuf `text` field; this is what the
|
||||
* ProPresenter UI renders next to the swatch).
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->label->getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the label carries an explicit color message (the absence of a
|
||||
* color is not the same as black: ProPresenter renders unset labels with
|
||||
* the default UI color).
|
||||
*/
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->label->setText($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasColor(): bool
|
||||
{
|
||||
return $this->label->hasColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label's color as an associative array, or null when unset.
|
||||
*
|
||||
* Channels are floats in the 0..1 range as stored by ProPresenter.
|
||||
*
|
||||
* @return array{r: float, g: float, b: float, a: float}|null
|
||||
*/
|
||||
public function getColor(): ?array
|
||||
|
|
@ -62,10 +51,6 @@ class Label
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: render the color as a 6-digit `#RRGGBB` hex string,
|
||||
* dropping alpha. Returns null when the label has no color.
|
||||
*/
|
||||
public function getColorHex(): ?string
|
||||
{
|
||||
$color = $this->getColor();
|
||||
|
|
@ -82,8 +67,48 @@ class Label
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the underlying protobuf Label message.
|
||||
* Set the label's color. Pass `null` to remove the color (the UI will
|
||||
* fall back to its default tint).
|
||||
*
|
||||
* @param array{r: float, g: float, b: float, a?: float}|null $color
|
||||
*/
|
||||
public function setColor(?array $color): self
|
||||
{
|
||||
if ($color === null) {
|
||||
$this->label->clearColor();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$proto = new Color();
|
||||
$proto->setRed((float) $color['r']);
|
||||
$proto->setGreen((float) $color['g']);
|
||||
$proto->setBlue((float) $color['b']);
|
||||
$proto->setAlpha((float) ($color['a'] ?? 1.0));
|
||||
$this->label->setColor($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience setter: accepts a `#RRGGBB` or `#RRGGBBAA` hex value and
|
||||
* applies it to the label. Alpha defaults to 1.0 when missing.
|
||||
*/
|
||||
public function setColorHex(string $hex): self
|
||||
{
|
||||
$hex = ltrim($hex, '#');
|
||||
if (!preg_match('/^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/', $hex)) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid hex color: %s', $hex));
|
||||
}
|
||||
|
||||
$r = hexdec(substr($hex, 0, 2)) / 255.0;
|
||||
$g = hexdec(substr($hex, 2, 2)) / 255.0;
|
||||
$b = hexdec(substr($hex, 4, 2)) / 255.0;
|
||||
$a = strlen($hex) === 8 ? hexdec(substr($hex, 6, 2)) / 255.0 : 1.0;
|
||||
|
||||
return $this->setColor(['r' => $r, 'g' => $g, 'b' => $b, 'a' => $a]);
|
||||
}
|
||||
|
||||
public function getProto(): LabelProto
|
||||
{
|
||||
return $this->label;
|
||||
|
|
|
|||
|
|
@ -4,17 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\Action\Label as LabelProto;
|
||||
use Rv\Data\Color;
|
||||
use Rv\Data\ProLabelsDocument;
|
||||
|
||||
/**
|
||||
* Wraps the protobuf {@see ProLabelsDocument} — the global ProPresenter
|
||||
* `Labels` file which lists every named label and its UI color.
|
||||
*
|
||||
* Labels are identified by name only (there is no UUID field). Names are
|
||||
* expected to be unique inside the document; if the source file violates
|
||||
* that, the first occurrence wins for {@see getLabelByName()} but every
|
||||
* label is preserved in {@see getLabels()} in document order.
|
||||
*/
|
||||
class LabelLibrary
|
||||
{
|
||||
/** @var Label[] */
|
||||
|
|
@ -30,16 +23,7 @@ class LabelLibrary
|
|||
private readonly ProLabelsDocument $document,
|
||||
) {
|
||||
foreach ($this->document->getLabels() as $labelProto) {
|
||||
$label = new Label($labelProto);
|
||||
$this->labels[] = $label;
|
||||
|
||||
$name = $label->getName();
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->labelsByName[$name] ??= $label;
|
||||
$this->labelsByNameLower[strtolower($name)] ??= $label;
|
||||
$this->register(new Label($labelProto));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,28 +40,95 @@ class LabelLibrary
|
|||
return count($this->labels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exact-name lookup. Use {@see findLabelByName()} for a
|
||||
* case-insensitive variant.
|
||||
*/
|
||||
public function getLabelByName(string $name): ?Label
|
||||
{
|
||||
return $this->labelsByName[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case-insensitive name lookup.
|
||||
*/
|
||||
public function findLabelByName(string $name): ?Label
|
||||
{
|
||||
return $this->labelsByNameLower[strtolower($name)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying protobuf ProLabelsDocument.
|
||||
* Append a brand-new label to the document.
|
||||
*
|
||||
* @param array{r: float, g: float, b: float, a?: float}|null $color
|
||||
*/
|
||||
public function addLabel(string $name, ?array $color = null): Label
|
||||
{
|
||||
$proto = new LabelProto();
|
||||
$proto->setText($name);
|
||||
if ($color !== null) {
|
||||
$colorProto = new Color();
|
||||
$colorProto->setRed((float) $color['r']);
|
||||
$colorProto->setGreen((float) $color['g']);
|
||||
$colorProto->setBlue((float) $color['b']);
|
||||
$colorProto->setAlpha((float) ($color['a'] ?? 1.0));
|
||||
$proto->setColor($colorProto);
|
||||
}
|
||||
|
||||
$existing = iterator_to_array($this->document->getLabels());
|
||||
$existing[] = $proto;
|
||||
$this->document->setLabels($existing);
|
||||
|
||||
$label = new Label($proto);
|
||||
$this->register($label);
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a label by its current name. Returns true when something was
|
||||
* removed.
|
||||
*/
|
||||
public function removeLabel(string $name): bool
|
||||
{
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($this->document->getLabels() as $proto) {
|
||||
if (!$removed && $proto->getText() === $name) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $proto;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->document->setLabels($kept);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDocument(): ProLabelsDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
private function register(Label $label): void
|
||||
{
|
||||
$this->labels[] = $label;
|
||||
|
||||
$name = $label->getName();
|
||||
if ($name === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->labelsByName[$name] ??= $label;
|
||||
$this->labelsByNameLower[strtolower($name)] ??= $label;
|
||||
}
|
||||
|
||||
private function rebuildIndex(): void
|
||||
{
|
||||
$this->labels = [];
|
||||
$this->labelsByName = [];
|
||||
$this->labelsByNameLower = [];
|
||||
foreach ($this->document->getLabels() as $proto) {
|
||||
$this->register(new Label($proto));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
src/LabelsFileWriter.php
Normal file
26
src/LabelsFileWriter.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class LabelsFileWriter
|
||||
{
|
||||
public static function write(LabelLibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$data = $library->getDocument()->serializeToString();
|
||||
$writtenBytes = file_put_contents($filePath, $data);
|
||||
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write Labels file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,16 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\Color;
|
||||
use Rv\Data\MacrosDocument\Macro as MacroProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
/**
|
||||
* Wraps a protobuf Macro, exposing its identifying fields (UUID, name) plus
|
||||
* convenience metadata (color, action count, image type, startup flag).
|
||||
*
|
||||
* Macros live in the global ProPresenter `Macros` document and may belong to
|
||||
* one or more {@see MacroCollection}s. Membership is resolved by
|
||||
* {@see MacroLibrary}, not by this wrapper.
|
||||
*/
|
||||
class Macro
|
||||
{
|
||||
public function __construct(
|
||||
|
|
@ -21,25 +15,33 @@ class Macro
|
|||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the macro's UUID as an upper-case string (empty when unset).
|
||||
*/
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->macro->getUuid()?->getString() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the macro's display name.
|
||||
*/
|
||||
public function setUuid(string $uuid): self
|
||||
{
|
||||
$proto = new UUID();
|
||||
$proto->setString($uuid);
|
||||
$this->macro->setUuid($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->macro->getName();
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->macro->setName($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the macro's color as an associative array, or null when unset.
|
||||
*
|
||||
* @return array{r: float, g: float, b: float, a: float}|null
|
||||
*/
|
||||
public function getColor(): ?array
|
||||
|
|
@ -59,35 +61,71 @@ class Macro
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether the macro is configured to fire on application startup.
|
||||
* @param array{r: float, g: float, b: float, a?: float}|null $color
|
||||
*/
|
||||
public function setColor(?array $color): self
|
||||
{
|
||||
if ($color === null) {
|
||||
$this->macro->clearColor();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$proto = new Color();
|
||||
$proto->setRed((float) $color['r']);
|
||||
$proto->setGreen((float) $color['g']);
|
||||
$proto->setBlue((float) $color['b']);
|
||||
$proto->setAlpha((float) ($color['a'] ?? 1.0));
|
||||
$this->macro->setColor($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTriggerOnStartup(): bool
|
||||
{
|
||||
return $this->macro->getTriggerOnStartup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of action entries attached to this macro.
|
||||
*
|
||||
* Action payloads are not exposed by this wrapper; use {@see getProto()} to
|
||||
* inspect them via the generated protobuf classes.
|
||||
*/
|
||||
public function setTriggerOnStartup(bool $value): self
|
||||
{
|
||||
$this->macro->setTriggerOnStartup($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getActionCount(): int
|
||||
{
|
||||
return count($this->macro->getActions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon enum value (see {@see \Rv\Data\MacrosDocument\Macro\ImageType}).
|
||||
*/
|
||||
public function getImageType(): int
|
||||
{
|
||||
return $this->macro->getImageType();
|
||||
}
|
||||
|
||||
public function setImageType(int $value): self
|
||||
{
|
||||
$this->macro->setImageType($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying protobuf Macro message.
|
||||
* Custom icon bytes (PNG/JPG). Empty when ProPresenter uses one of the
|
||||
* built-in `image_type` icons.
|
||||
*/
|
||||
public function getImageData(): string
|
||||
{
|
||||
return $this->macro->getImageData();
|
||||
}
|
||||
|
||||
public function setImageData(string $bytes): self
|
||||
{
|
||||
$this->macro->setImageData($bytes);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProto(): MacroProto
|
||||
{
|
||||
return $this->macro;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,9 @@ declare(strict_types=1);
|
|||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\MacrosDocument\MacroCollection as MacroCollectionProto;
|
||||
use Rv\Data\MacrosDocument\MacroCollection\Item as ItemProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
/**
|
||||
* Wraps a protobuf MacroCollection. A collection groups macros by UUID
|
||||
* reference; macro definitions themselves live at the document root.
|
||||
*/
|
||||
class MacroCollection
|
||||
{
|
||||
public function __construct(
|
||||
|
|
@ -17,28 +15,33 @@ class MacroCollection
|
|||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection's UUID as a string (empty when unset).
|
||||
*/
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->collection->getUuid()?->getString() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection's display name (e.g. "Ablauf").
|
||||
*/
|
||||
public function setUuid(string $uuid): self
|
||||
{
|
||||
$proto = new UUID();
|
||||
$proto->setString($uuid);
|
||||
$this->collection->setUuid($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->collection->getName();
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->collection->setName($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UUIDs of macros referenced by this collection, in order.
|
||||
*
|
||||
* Items in the protobuf use a `oneof` ItemType — currently only
|
||||
* `macro_id` is defined. Items without a populated reference are skipped.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMacroUuids(): array
|
||||
|
|
@ -55,8 +58,39 @@ class MacroCollection
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the underlying protobuf MacroCollection message.
|
||||
* Replace the collection's referenced macro UUIDs in one call. Pass UUID
|
||||
* strings exactly as ProPresenter writes them (upper-case is conventional).
|
||||
*
|
||||
* @param string[] $uuids
|
||||
*/
|
||||
public function setMacroUuids(array $uuids): self
|
||||
{
|
||||
$items = [];
|
||||
foreach ($uuids as $uuid) {
|
||||
$item = new ItemProto();
|
||||
$ref = new UUID();
|
||||
$ref->setString($uuid);
|
||||
$item->setMacroId($ref);
|
||||
$items[] = $item;
|
||||
}
|
||||
$this->collection->setItems($items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addMacroUuid(string $uuid): self
|
||||
{
|
||||
$items = iterator_to_array($this->collection->getItems());
|
||||
$item = new ItemProto();
|
||||
$ref = new UUID();
|
||||
$ref->setString($uuid);
|
||||
$item->setMacroId($ref);
|
||||
$items[] = $item;
|
||||
$this->collection->setItems($items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProto(): MacroCollectionProto
|
||||
{
|
||||
return $this->collection;
|
||||
|
|
|
|||
|
|
@ -5,16 +5,10 @@ declare(strict_types=1);
|
|||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\MacrosDocument;
|
||||
use Rv\Data\MacrosDocument\Macro as MacroProto;
|
||||
use Rv\Data\MacrosDocument\MacroCollection as MacroCollectionProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
/**
|
||||
* Wraps a protobuf MacrosDocument — the global ProPresenter `Macros` file
|
||||
* which lists every macro definition and the collections that group them.
|
||||
*
|
||||
* Lookup helpers index macros and collections by UUID (case-insensitive) and
|
||||
* by name, mirroring the convention used by {@see Song}. Collection
|
||||
* membership is resolved by mapping {@see MacroCollection::getMacroUuids()}
|
||||
* back through this library.
|
||||
*/
|
||||
class MacroLibrary
|
||||
{
|
||||
/** @var Macro[] */
|
||||
|
|
@ -41,43 +35,7 @@ class MacroLibrary
|
|||
public function __construct(
|
||||
private readonly MacrosDocument $document,
|
||||
) {
|
||||
foreach ($this->document->getMacros() as $macroProto) {
|
||||
$macro = new Macro($macroProto);
|
||||
$this->macros[] = $macro;
|
||||
|
||||
$uuid = strtoupper($macro->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->macrosByUuid[$uuid] = $macro;
|
||||
}
|
||||
|
||||
$name = $macro->getName();
|
||||
if ($name !== '') {
|
||||
$this->macrosByName[$name] = $macro;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->document->getMacroCollections() as $collectionProto) {
|
||||
$collection = new MacroCollection($collectionProto);
|
||||
$this->collections[] = $collection;
|
||||
|
||||
$uuid = strtoupper($collection->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->collectionsByUuid[$uuid] = $collection;
|
||||
}
|
||||
|
||||
$name = $collection->getName();
|
||||
if ($name !== '') {
|
||||
$this->collectionsByName[$name] = $collection;
|
||||
}
|
||||
|
||||
foreach ($collection->getMacroUuids() as $macroUuid) {
|
||||
$key = strtoupper($macroUuid);
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$this->collectionsByMacroUuid[$key][] = $collection;
|
||||
}
|
||||
}
|
||||
$this->rebuildIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -117,10 +75,6 @@ class MacroLibrary
|
|||
}
|
||||
|
||||
/**
|
||||
* Resolve a collection's referenced macros to {@see Macro} wrappers.
|
||||
*
|
||||
* Unknown UUIDs (referenced but not defined at document root) are skipped.
|
||||
*
|
||||
* @return Macro[]
|
||||
*/
|
||||
public function getMacrosForCollection(MacroCollection $collection): array
|
||||
|
|
@ -137,10 +91,6 @@ class MacroLibrary
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the collections a macro belongs to (membership is by UUID
|
||||
* reference). A macro may legally appear in zero, one, or many
|
||||
* collections.
|
||||
*
|
||||
* @return MacroCollection[]
|
||||
*/
|
||||
public function getCollectionsForMacro(Macro $macro): array
|
||||
|
|
@ -154,10 +104,141 @@ class MacroLibrary
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the underlying protobuf MacrosDocument.
|
||||
* Append a brand-new macro to the document.
|
||||
*/
|
||||
public function addMacro(string $name, string $uuid): Macro
|
||||
{
|
||||
$proto = new MacroProto();
|
||||
$uuidProto = new UUID();
|
||||
$uuidProto->setString($uuid);
|
||||
$proto->setUuid($uuidProto);
|
||||
$proto->setName($name);
|
||||
|
||||
$existing = iterator_to_array($this->document->getMacros());
|
||||
$existing[] = $proto;
|
||||
$this->document->setMacros($existing);
|
||||
|
||||
$this->rebuildIndex();
|
||||
|
||||
return $this->getMacroByUuid($uuid) ?? new Macro($proto);
|
||||
}
|
||||
|
||||
public function removeMacro(string $uuid): bool
|
||||
{
|
||||
$needle = strtoupper($uuid);
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($this->document->getMacros() as $proto) {
|
||||
$current = strtoupper($proto->getUuid()?->getString() ?? '');
|
||||
if (!$removed && $current === $needle) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $proto;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->document->setMacros($kept);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addCollection(string $name, string $uuid): MacroCollection
|
||||
{
|
||||
$proto = new MacroCollectionProto();
|
||||
$uuidProto = new UUID();
|
||||
$uuidProto->setString($uuid);
|
||||
$proto->setUuid($uuidProto);
|
||||
$proto->setName($name);
|
||||
|
||||
$existing = iterator_to_array($this->document->getMacroCollections());
|
||||
$existing[] = $proto;
|
||||
$this->document->setMacroCollections($existing);
|
||||
|
||||
$this->rebuildIndex();
|
||||
|
||||
return $this->getCollectionByUuid($uuid) ?? new MacroCollection($proto);
|
||||
}
|
||||
|
||||
public function removeCollection(string $uuid): bool
|
||||
{
|
||||
$needle = strtoupper($uuid);
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($this->document->getMacroCollections() as $proto) {
|
||||
$current = strtoupper($proto->getUuid()?->getString() ?? '');
|
||||
if (!$removed && $current === $needle) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $proto;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->document->setMacroCollections($kept);
|
||||
$this->rebuildIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDocument(): MacrosDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
private function rebuildIndex(): void
|
||||
{
|
||||
$this->macros = [];
|
||||
$this->collections = [];
|
||||
$this->macrosByUuid = [];
|
||||
$this->macrosByName = [];
|
||||
$this->collectionsByUuid = [];
|
||||
$this->collectionsByName = [];
|
||||
$this->collectionsByMacroUuid = [];
|
||||
|
||||
foreach ($this->document->getMacros() as $macroProto) {
|
||||
$macro = new Macro($macroProto);
|
||||
$this->macros[] = $macro;
|
||||
|
||||
$uuid = strtoupper($macro->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->macrosByUuid[$uuid] = $macro;
|
||||
}
|
||||
|
||||
$name = $macro->getName();
|
||||
if ($name !== '') {
|
||||
$this->macrosByName[$name] = $macro;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->document->getMacroCollections() as $collectionProto) {
|
||||
$collection = new MacroCollection($collectionProto);
|
||||
$this->collections[] = $collection;
|
||||
|
||||
$uuid = strtoupper($collection->getUuid());
|
||||
if ($uuid !== '') {
|
||||
$this->collectionsByUuid[$uuid] = $collection;
|
||||
}
|
||||
|
||||
$name = $collection->getName();
|
||||
if ($name !== '') {
|
||||
$this->collectionsByName[$name] = $collection;
|
||||
}
|
||||
|
||||
foreach ($collection->getMacroUuids() as $macroUuid) {
|
||||
$key = strtoupper($macroUuid);
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$this->collectionsByMacroUuid[$key][] = $collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
src/MacrosFileWriter.php
Normal file
26
src/MacrosFileWriter.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class MacrosFileWriter
|
||||
{
|
||||
public static function write(MacroLibrary $library, string $filePath): void
|
||||
{
|
||||
$directory = dirname($filePath);
|
||||
if (!is_dir($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Target directory does not exist: %s', $directory));
|
||||
}
|
||||
|
||||
$data = $library->getDocument()->serializeToString();
|
||||
$writtenBytes = file_put_contents($filePath, $data);
|
||||
|
||||
if ($writtenBytes === false) {
|
||||
throw new RuntimeException(sprintf('Unable to write Macros file: %s', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/Message.php
Normal file
103
src/Message.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\Message as MessageProto;
|
||||
use Rv\Data\UUID;
|
||||
|
||||
class Message
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MessageProto $message,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->message->getUuid()?->getString() ?? '';
|
||||
}
|
||||
|
||||
public function setUuid(string $uuid): self
|
||||
{
|
||||
$proto = new UUID();
|
||||
$proto->setString($uuid);
|
||||
$this->message->setUuid($proto);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->message->getTitle();
|
||||
}
|
||||
|
||||
public function setTitle(string $title): self
|
||||
{
|
||||
$this->message->setTitle($title);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimeToRemove(): float
|
||||
{
|
||||
return $this->message->getTimeToRemove();
|
||||
}
|
||||
|
||||
public function setTimeToRemove(float $timeToRemove): self
|
||||
{
|
||||
$this->message->setTimeToRemove($timeToRemove);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isVisibleOnNetwork(): bool
|
||||
{
|
||||
return $this->message->getVisibleOnNetwork();
|
||||
}
|
||||
|
||||
public function setVisibleOnNetwork(bool $visibleOnNetwork): self
|
||||
{
|
||||
$this->message->setVisibleOnNetwork($visibleOnNetwork);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMessageText(): string
|
||||
{
|
||||
return $this->message->getMessageText();
|
||||
}
|
||||
|
||||
public function setMessageText(string $messageText): self
|
||||
{
|
||||
$this->message->setMessageText($messageText);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getClearType(): int
|
||||
{
|
||||
return $this->message->getClearType();
|
||||
}
|
||||
|
||||
public function setClearType(int $clearType): self
|
||||
{
|
||||
$this->message->setClearType($clearType);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed raw repeated \Rv\Data\Message\Token protos
|
||||
*/
|
||||
public function getTokens(): mixed
|
||||
{
|
||||
return $this->message->getTokens();
|
||||
}
|
||||
|
||||
public function getProto(): MessageProto
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue