docs: prepare project for open-source release on GitHub

- Add MIT LICENSE (Thorsten Buss) with attribution to the upstream
  MIT-licensed greyshirtguy/ProPresenter7-Proto definitions.
- Add comprehensive README.md: badges, feature matrix, install,
  seven runnable getting-started examples (read/modify/generate
  songs, playlists, bundles, global libraries), CLI tool reference,
  documentation index, project structure, caveats.
- Update composer.json with package name (bussnet/propresenter7-php-api),
  MIT license, keywords, author, homepage, support URLs, dev autoload,
  and a `composer test` script.
- Polish doc/INDEX.md, doc/keywords.md, and doc/CONTRIBUTING.md so they
  read well for both humans and AI assistants; remove README-duplicate
  content from INDEX.md and link to the top-level README instead.
- Expand .gitignore to cover IDE/OS metadata and agent workspaces.

All 370 tests still pass (9,200 assertions). README examples #3 and #5
verified end-to-end (generate -> read back -> assert metadata).
This commit is contained in:
Thorsten Bus 2026-05-03 21:59:39 +02:00
parent 9e3e719806
commit 5ac27d676c
8 changed files with 673 additions and 129 deletions

28
.gitignore vendored
View file

@ -1,4 +1,26 @@
.sisyphus
.php-cs-fixer.cache
# Composer
/vendor/
composer.phar
# PHPUnit
.phpunit.result.cache
vendor/
/build/
/coverage/
# PHP CS Fixer
.php-cs-fixer.cache
.php_cs.cache
# IDE / editor metadata
.idea/
.vscode/
*.swp
*.swo
*~
# macOS
.DS_Store
# Internal scratch / agent workspaces
.sisyphus/
.w/

29
LICENSE Normal file
View file

@ -0,0 +1,29 @@
MIT License
Copyright (c) 2026 Thorsten Buss
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## Third-Party Notices
This project bundles `.proto` files derived from
[greyshirtguy/ProPresenter7-Proto](https://github.com/greyshirtguy/ProPresenter7-Proto)
(v7.16.2), which is also distributed under the MIT License.

523
README.md
View file

@ -0,0 +1,523 @@
# ProPresenter 7 PHP API
> A PHP library to **read, modify, and generate** [ProPresenter 7](https://renewedvision.com/propresenter/) files — songs, playlists, bundles, themes, and global library files.
[![PHP Version](https://img.shields.io/badge/php-%5E8.4-777bb4.svg)](https://www.php.net/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![Tests](https://img.shields.io/badge/tests-370%20passing-brightgreen.svg)](#development)
[![Built on Protocol Buffers](https://img.shields.io/badge/format-protobuf-4285F4.svg)](https://protobuf.dev/)
ProPresenter 7 stores its data in protobuf-encoded binary files (with ZIP wrappers for playlists and bundles). This library decodes those formats into idiomatic PHP objects, lets you modify them, and writes them back out — with full round-trip fidelity for global library files and verified compatibility with PP7 for songs and bundles.
---
## Table of Contents
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [1. Read a song (`.pro`)](#1-read-a-song-pro)
- [2. Modify and save a song](#2-modify-and-save-a-song)
- [3. Generate a song from scratch](#3-generate-a-song-from-scratch)
- [4. Read a playlist (`.proplaylist`)](#4-read-a-playlist-proplaylist)
- [5. Generate a playlist](#5-generate-a-playlist)
- [6. Work with a `.probundle`](#6-work-with-a-probundle)
- [7. Read a global library file](#7-read-a-global-library-file)
- [CLI Tools](#cli-tools)
- [Documentation](#documentation)
- [Project Structure](#project-structure)
- [Development](#development)
- [Compatibility & Caveats](#compatibility--caveats)
- [Contributing](#contributing)
- [License](#license)
- [Credits](#credits)
---
## Features
### File formats supported
| Format | Extension | Read | Modify | Generate | Notes |
|--------|-----------|:----:|:------:|:--------:|-------|
| Song | `.pro` | ✅ | ✅ | ✅ | Lyrics, groups, slides, arrangements, translations, CCLI metadata, macros, media |
| Playlist | `.proplaylist` | ✅ | ✅ | ✅ | ZIP64 archive, embedded songs, headers, placeholders |
| Bundle | `.probundle` | ✅ | ✅ | ✅ | ZIP archive containing a song + flat media assets |
| Theme | folder | ✅ | ✅ | ✅ | `Theme` protobuf + `Assets/` directory |
| Macros | `Macros` | ✅ | ✅ | — | Macros + collections |
| Labels | `Labels` | ✅ | ✅ | — | Slide labels with optional UI colors |
| Groups | `Groups` | ✅ | ✅ | — | Library groups (UUID, color, hot keys) |
| ClearGroups | `ClearGroups` | ✅ | ✅ | — | Clear-action groups |
| CCLI | `CCLI` | ✅ | ✅ | — | License, copyright template |
| Messages | `Messages` | ✅ | ✅ | — | Lower-third / overlay messages |
| Timers | `Timers` | ✅ | ✅ | — | Timer definitions + clock format |
| Stage | `Stage` | ✅ | ✅ | — | Stage display layouts |
| Workspace | `Workspace` | ✅ | ✅ | — | Screens, looks, masks, audio/video inputs |
| Props | `Props` | ✅ | ✅ | — | Prop cues + transitions |
| TestPatterns | `TestPatterns` | ✅ | ✅ | — | Test pattern overrides |
| Calendar | `Calendar` | ✅ | ✅ | — | Scheduled events firing macros |
| KeyMappings | `KeyMappings` | ✅ | ✅ | — | Custom hot-key bindings |
| CommunicationDevices | JSON | ✅ | ✅ | — | MIDI / serial / OSC bindings |
### Highlights
- **High-level wrappers** — work with `Song`, `Group`, `Slide`, `Arrangement`, `PlaylistArchive` etc. instead of raw protobuf classes.
- **RTF text extraction**`Slide::getPlainText()` returns clean text from ProPresenter's CocoaRTF, including German umlauts and Unicode.
- **Translation-aware** — read and write multi-language slides (`hasTranslation()`, `getTranslation()`).
- **ZIP64 repair** — automatically fixes ProPresenter's 98-byte ZIP64 header bug on read.
- **Generate from scratch** — build complete `.pro` and `.proplaylist` files programmatically with media references.
- **18 CLI tools** — quickly inspect any ProPresenter file from the command line.
- **370 tests, 9,200+ assertions** — verified against 169 real-world reference songs from production worship environments.
- **Comprehensive docs** — every API and binary format is documented in [`doc/`](doc/).
---
## Requirements
- **PHP 8.4** or higher
- [`google/protobuf`](https://github.com/protocolbuffers/protobuf-php) (installed via Composer)
- [`ext-zip`](https://www.php.net/manual/en/book.zip.php) for `.proplaylist` and `.probundle` files (bundled with most PHP distributions)
---
## Installation
```bash
composer require bussnet/propresenter7-php-api
```
Or clone the repository to develop locally:
```bash
git clone https://github.com/bussnet/propresenter7-php-api.git
cd propresenter7-php-api
composer install
```
---
## Getting Started
All examples assume Composer's autoloader is loaded:
```php
require 'vendor/autoload.php';
```
### 1. Read a song (`.pro`)
```php
use ProPresenter\Parser\ProFileReader;
$song = ProFileReader::read('path/to/Amazing Grace.pro');
echo $song->getName() . "\n"; // "Amazing Grace"
echo $song->getCcliAuthor() . "\n"; // "John Newton"
echo $song->getCcliCopyrightYear() . "\n"; // 1779
// Walk groups → slides → text
foreach ($song->getGroups() as $group) {
echo "[{$group->getName()}]\n";
foreach ($song->getSlidesForGroup($group) as $slide) {
echo " " . $slide->getPlainText() . "\n";
if ($slide->hasTranslation()) {
echo " → " . $slide->getTranslation()->getPlainText() . "\n";
}
}
}
// Resolve an arrangement to a flat list of groups (in performance order)
$arrangement = $song->getArrangements()[0];
foreach ($song->getGroupsForArrangement($arrangement) as $group) {
echo $group->getName() . " → ";
}
```
### 2. Modify and save a song
```php
use ProPresenter\Parser\ProFileReader;
use ProPresenter\Parser\ProFileWriter;
$song = ProFileReader::read('input.pro');
// Update CCLI metadata
$song->setName('Amazing Grace (My Chains Are Gone)');
$song->setCcliPublisher('Public Domain');
$song->setCcliCopyrightYear(2006);
// Rename a group
$song->getGroupByName('Verse 1')?->setName('Strophe 1');
// Add a label to the first slide
$song->getSlides()[0]->setLabel('Intro');
ProFileWriter::write($song, 'output.pro');
```
### 3. Generate a song from scratch
```php
use ProPresenter\Parser\ProFileGenerator;
ProFileGenerator::generateAndWrite(
'amazing-grace.pro',
'Amazing Grace',
[
[
'name' => 'Verse 1',
'color' => [0.13, 0.59, 0.95, 1.0], // RGBA floats (0..1)
'slides' => [
['text' => "Amazing grace, how sweet the sound\nThat saved a wretch like me"],
[
'text' => 'I once was lost, but now am found',
'translation' => 'Ich war verloren, doch jetzt gefunden',
],
],
],
[
'name' => 'Chorus',
'color' => [0.95, 0.27, 0.27, 1.0],
'slides' => [
['text' => 'My chains are gone, I have been set free'],
],
],
],
[
['name' => 'normal', 'groupNames' => ['Verse 1', 'Chorus', 'Verse 1', 'Chorus']],
],
[
'author' => 'John Newton',
'song_title' => 'Amazing Grace',
'copyright_year' => 1779,
],
);
```
### 4. Read a playlist (`.proplaylist`)
```php
use ProPresenter\Parser\ProPlaylistReader;
$archive = ProPlaylistReader::read('Sunday Service.proplaylist');
echo $archive->getName() . "\n";
foreach ($archive->getEntries() as $entry) {
echo match ($entry->getType()) {
'header' => "── {$entry->getName()} ──\n",
'presentation' => " ♪ {$entry->getName()} (arr: " . ($entry->getArrangementName() ?? 'default') . ")\n",
'placeholder' => " · {$entry->getName()} (TBD)\n",
default => " ? {$entry->getName()}\n",
};
// Lazily parse embedded .pro files
if ($entry->getType() === 'presentation') {
$song = $archive->getEmbeddedSong($entry);
if ($song !== null) {
echo " → " . count($song->getSlides()) . " slides\n";
}
}
}
```
### 5. Generate a playlist
```php
use ProPresenter\Parser\ProPlaylistGenerator;
ProPlaylistGenerator::generateAndWrite(
'sunday-service.proplaylist',
'Sunday Service',
[
['type' => 'header', 'name' => 'Worship', 'color' => [0.95, 0.27, 0.27, 1.0]],
['type' => 'presentation', 'name' => 'Amazing Grace', 'path' => 'file:///Songs/amazing-grace.pro', 'arrangement' => 'normal'],
['type' => 'presentation', 'name' => 'Oceans', 'path' => 'file:///Songs/oceans.pro'],
['type' => 'header', 'name' => 'Sermon'],
['type' => 'placeholder', 'name' => 'Sermon notes'],
],
['notes' => 'Sunday morning service'],
);
```
### 6. Work with a `.probundle`
A `.probundle` is a ZIP archive containing a single `.pro` file plus its referenced media — perfect for sharing presentations between machines.
```php
use ProPresenter\Parser\ProBundleReader;
use ProPresenter\Parser\ProBundleWriter;
use ProPresenter\Parser\PresentationBundle;
use ProPresenter\Parser\ProFileGenerator;
// Read
$bundle = ProBundleReader::read('Christmas Slides.probundle');
echo $bundle->getName() . "\n";
echo $bundle->getMediaFileCount() . " media files\n";
foreach ($bundle->getMediaFiles() as $filename => $bytes) {
echo " $filename: " . strlen($bytes) . " bytes\n";
}
// Build a new bundle (media uses ROOT_CURRENT_RESOURCE → portable across machines)
$song = ProFileGenerator::generate(
'My Slides',
[[
'name' => 'Background',
'color' => [0.2, 0.2, 0.2, 1.0],
'slides' => [[
'media' => 'background.png',
'format' => 'png',
'label' => 'background.png',
'bundleRelative' => true,
]],
]],
[['name' => 'normal', 'groupNames' => ['Background']]],
);
$bundle = new PresentationBundle(
$song,
'My Slides.pro',
['background.png' => file_get_contents('background.png')],
);
ProBundleWriter::write($bundle, 'my-slides.probundle');
```
### 7. Read a global library file
ProPresenter stores its global library in extension-less protobuf files inside the user library folder. Each is exposed through a dedicated reader/writer:
```php
use ProPresenter\Parser\MacrosFileReader;
use ProPresenter\Parser\MacrosFileWriter;
$library = MacrosFileReader::read('/path/to/Macros');
foreach ($library->getMacros() as $macro) {
echo $macro->getName() . " — " . $macro->getUuid() . "\n";
}
// Add a macro programmatically
$library->addMacro('Service Start', '00000000-0000-0000-0000-000000000001');
$library->getMacroByName('Service Start')?->setColor(['r' => 0.0, 'g' => 0.5, 'b' => 1.0]);
MacrosFileWriter::write($library, '/path/to/Macros');
```
The same `Reader::read()` / `Writer::write()` pattern applies to every global library file. See [doc/api/](doc/api/) for the full set.
---
## CLI Tools
Every supported file type ships with an inspector script in [`bin/`](bin/):
```bash
php bin/parse-song.php path/to/song.pro
php bin/parse-playlist.php path/to/playlist.proplaylist
php bin/parse-theme.php path/to/ThemeFolder
php bin/parse-macros.php ~/Library/.../Macros
php bin/parse-labels.php ~/Library/.../Labels
php bin/parse-groups.php ~/Library/.../Groups
php bin/parse-clear-groups.php ~/Library/.../ClearGroups
php bin/parse-ccli.php ~/Library/.../CCLI
php bin/parse-messages.php ~/Library/.../Messages
php bin/parse-timers.php ~/Library/.../Timers
php bin/parse-stage.php ~/Library/.../Stage
php bin/parse-workspace.php ~/Library/.../Workspace
php bin/parse-props.php ~/Library/.../Props
php bin/parse-test-patterns.php ~/Library/.../TestPatterns
php bin/parse-calendar.php ~/Library/.../Calendar
php bin/parse-key-mappings.php ~/Library/.../KeyMappings
php bin/parse-communication-devices.php ~/Library/.../CommunicationDevices
```
Example output for `parse-song.php`:
```text
Song: Amazing Grace
UUID: A1B2C3D4-...
CCLI Metadata:
Song Title: Amazing Grace
Author: John Newton
Copyright Year: 1779
Display: yes
Groups (3):
[1] Verse 1 (2 slides)
Slide 1: Amazing grace, how sweet the sound / That saved a wretch like me
Slide 2: I once was lost, but now am found
[2] Chorus (1 slide)
Slide 1: My chains are gone, I have been set free
...
Arrangements (1):
[1] normal: Verse 1 -> Chorus -> Verse 1 -> Chorus
```
---
## Documentation
Full documentation lives in [`doc/`](doc/) — start with **[doc/INDEX.md](doc/INDEX.md)**.
### API reference
| Topic | Document |
|-------|----------|
| Songs (`.pro`) | [doc/api/song.md](doc/api/song.md) |
| Playlists (`.proplaylist`) | [doc/api/playlist.md](doc/api/playlist.md) |
| Bundles (`.probundle`) | [doc/api/bundle.md](doc/api/bundle.md) |
| Themes (folder) | [doc/api/theme.md](doc/api/theme.md) |
| Macros library | [doc/api/macros.md](doc/api/macros.md) |
| Labels library | [doc/api/labels.md](doc/api/labels.md) |
| Groups library | [doc/api/groups.md](doc/api/groups.md) |
| ClearGroups library | [doc/api/clear-groups.md](doc/api/clear-groups.md) |
| CCLI settings | [doc/api/ccli.md](doc/api/ccli.md) |
| Messages library | [doc/api/messages.md](doc/api/messages.md) |
| Timers library | [doc/api/timers.md](doc/api/timers.md) |
| Stage layouts | [doc/api/stage.md](doc/api/stage.md) |
| Workspace | [doc/api/workspace.md](doc/api/workspace.md) |
| Props library | [doc/api/props.md](doc/api/props.md) |
| TestPatterns | [doc/api/test-patterns.md](doc/api/test-patterns.md) |
| Calendar | [doc/api/calendar.md](doc/api/calendar.md) |
| KeyMappings | [doc/api/key-mappings.md](doc/api/key-mappings.md) |
| CommunicationDevices | [doc/api/communication-devices.md](doc/api/communication-devices.md) |
### Binary format specifications
| Format | Document |
|--------|----------|
| `.pro` (songs) | [doc/formats/pp_song_spec.md](doc/formats/pp_song_spec.md) |
| `.proplaylist` | [doc/formats/pp_playlist_spec.md](doc/formats/pp_playlist_spec.md) |
| `.probundle` | [doc/formats/pp_bundle_spec.md](doc/formats/pp_bundle_spec.md) |
### Search by keyword
Looking for something specific? Use the keyword index: [doc/keywords.md](doc/keywords.md).
---
## Project Structure
```text
.
├── bin/ # 18 CLI tools (parse-*.php scripts)
├── src/ # PHP source (wrappers, readers, writers, generators)
├── generated/ # Auto-generated protobuf PHP classes (Rv\Data\…)
├── proto/ # Vendored .proto files (greyshirtguy/ProPresenter7-Proto v7.16.2)
├── tests/ # PHPUnit test suite (370 tests)
├── doc/
│ ├── INDEX.md # Documentation entry point
│ ├── keywords.md # Keyword search index
│ ├── CONTRIBUTING.md # Documentation guidelines
│ ├── api/ # PHP API documentation
│ ├── formats/ # Binary file format specifications
│ ├── internal/ # Development notes (learnings, decisions, issues)
│ └── reference_samples/ # Reference files used by tests (real-world songs)
├── composer.json
├── phpunit.xml
├── LICENSE
└── README.md
```
### Key classes
| Class | Purpose |
|-------|---------|
| `ProPresenter\Parser\Song` | Top-level song wrapper (groups + slides + arrangements) |
| `ProPresenter\Parser\Group` | Song part (verse, chorus, …) |
| `ProPresenter\Parser\Slide` | Single slide with text, label, macro, media |
| `ProPresenter\Parser\TextElement` | Text element with RTF + plain-text accessors |
| `ProPresenter\Parser\Arrangement` | Group order for a performance |
| `ProPresenter\Parser\PlaylistArchive` | `.proplaylist` ZIP wrapper |
| `ProPresenter\Parser\PresentationBundle` | `.probundle` ZIP wrapper |
| `ProPresenter\Parser\ThemeBundle` | Theme folder wrapper |
| `ProPresenter\Parser\ProFileReader` / `Writer` / `Generator` | `.pro` IO |
| `ProPresenter\Parser\ProPlaylistReader` / `Writer` / `Generator` | `.proplaylist` IO |
| `ProPresenter\Parser\ProBundleReader` / `Writer` | `.probundle` IO |
| `ProPresenter\Parser\Zip64Fixer` | Repairs ProPresenter's broken ZIP64 EOCD headers |
| `ProPresenter\Parser\RtfExtractor` | Standalone CocoaRTF → plain-text converter |
---
## Development
### Running the tests
```bash
composer install
composer test
```
You should see:
```text
PHPUnit 11.5.55 by Sebastian Bergmann and contributors.
OK (370 tests, 9200 assertions)
```
The test suite includes:
- **Unit tests** — every wrapper class
- **Integration tests** — readers + writers round-tripping reference files
- **Mass validation** — parses 169 real-world `.pro` songs (`tests/MassValidationTest.php`)
- **Binary fidelity tests** — verifies byte-perfect round-trips for global library files
### Reference samples
Real ProPresenter files used by the tests live in [`doc/reference_samples/`](doc/reference_samples/). They are exported from production worship environments and cover edge cases (translations, missing arrangements, ZIP64 quirks, German Unicode, embedded media).
### Regenerating sample bundles
Some test fixtures are generated procedurally:
```bash
php bin/regen-test-bundles.php
```
---
## Compatibility & Caveats
- **Verified against** ProPresenter 7.16+ on macOS. Files generated by this library open cleanly in ProPresenter 7.
- **Round-trip fidelity** — global library files (`Macros`, `Labels`, `Groups`, …) round-trip byte-for-byte. Songs do **not**: ProPresenter's protobuf schema contains undocumented fields that are dropped on re-encode. The library preserves logical content perfectly, but raw bytes will differ. See [doc/internal/issues.md](doc/internal/issues.md) for the gory details.
- **ZIP64 quirk** — ProPresenter exports `.proplaylist` and `.probundle` files with a 98-byte ZIP64 header offset bug. `Zip64Fixer` patches this in memory before parsing. Files written by this library use clean standard ZIPs.
- **RTF** — slide text is stored as CocoaRTF (Windows-1252 with `\'xx` hex escapes for non-ASCII). `getPlainText()` decodes this; the generator produces clean RTF that PP7 accepts.
- **macOS-centric paths** — ProPresenter uses `file://` URLs with absolute paths in some fields. For portable bundles, use `'bundleRelative' => true` on media slides (this sets `ROOT_CURRENT_RESOURCE` so PP7 resolves media relative to the archive).
---
## Contributing
Contributions are welcome! Please:
1. Open an issue describing the change before sending a PR for anything non-trivial.
2. Follow the documentation guidelines in [doc/CONTRIBUTING.md](doc/CONTRIBUTING.md).
3. Add a test for any new behavior — TDD is the convention here.
4. Run `composer test` before submitting.
5. Keep changes focused; avoid unrelated refactors.
---
## License
This project is released under the [MIT License](LICENSE).
The bundled `.proto` files in [`proto/`](proto/) are derived from [greyshirtguy/ProPresenter7-Proto](https://github.com/greyshirtguy/ProPresenter7-Proto) v7.16.2, also distributed under the MIT License.
---
## Credits
- **[Renewed Vision](https://renewedvision.com/)** — for ProPresenter, an excellent presentation tool.
- **[greyshirtguy](https://github.com/greyshirtguy/ProPresenter7-Proto)** — for reverse-engineering the ProPresenter 7 protobuf schema, without which this library would not exist.
- **[Google Protocol Buffers](https://protobuf.dev/)** — for the underlying serialization format.
ProPresenter is a trademark of Renewed Vision, LLC. This project is not affiliated with or endorsed by Renewed Vision.

View file

@ -1,7 +1,32 @@
{
"name": "propresenter/parser",
"description": "ProPresenter song file parser",
"name": "bussnet/propresenter7-php-api",
"description": "PHP library to read, modify, and generate ProPresenter 7 files (.pro songs, .proplaylist, .probundle, themes, and global library files).",
"type": "library",
"license": "MIT",
"keywords": [
"propresenter",
"propresenter7",
"presentation",
"worship",
"church",
"protobuf",
"parser",
"ccli",
"pro-file",
"proplaylist",
"probundle"
],
"homepage": "https://github.com/bussnet/propresenter7-php-api",
"support": {
"issues": "https://github.com/bussnet/propresenter7-php-api/issues",
"source": "https://github.com/bussnet/propresenter7-php-api"
},
"authors": [
{
"name": "Thorsten Buss",
"role": "Developer"
}
],
"require": {
"php": "^8.4",
"google/protobuf": "^4.0"
@ -15,5 +40,21 @@
"Rv\\Data\\": "generated/Rv/Data/",
"GPBMetadata\\": "generated/GPBMetadata/"
}
},
"autoload-dev": {
"psr-4": {
"ProPresenter\\Parser\\Tests\\": "tests/"
}
},
"bin": [
"bin/parse-song.php",
"bin/parse-playlist.php"
],
"scripts": {
"test": "phpunit",
"test:coverage": "phpunit --coverage-text"
},
"config": {
"sort-packages": true
}
}

2
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b110e80f59c12eeb362683937b239e85",
"content-hash": "b394c8640857fda9e30e27c0910b9a2b",
"packages": [
{
"name": "google/protobuf",

View file

@ -1,14 +1,16 @@
# Documentation Guidelines
> How to maintain and extend the `doc/` directory for future AI agents and developers.
> How to maintain and extend the `doc/` directory.
>
> These rules keep the documentation easy to skim for both humans and AI assistants. For general project contribution rules (issues, PRs, tests), see the top-level [README](../README.md#contributing).
## Principles
1. **Load only what you need.** Each doc is self-contained for its topic. No doc should require reading other docs to be useful.
2. **One topic per file.** Don't merge unrelated topics. Create a new file instead.
3. **Keyword-searchable.** Every new doc must be added to `keywords.md`.
1. **Self-contained docs.** Each file should be useful on its own. Cross-link generously, but don't force readers to load three other files just to understand one.
2. **One topic per file.** Don't merge unrelated topics — create a new file instead.
3. **Keyword-searchable.** Every new doc must be added to [keywords.md](keywords.md).
4. **Code examples over prose.** Show the API call, not a paragraph explaining it.
5. **Keep it current.** When you change code, update the corresponding doc.
5. **Keep it current.** When you change code, update the corresponding doc in the same PR.
---
@ -154,21 +156,21 @@ When modifying the codebase:
---
## AI Agent Instructions
## Tips for AI Assistants
When AGENTS.md tells you to load docs, follow these steps:
The docs are also designed to be easy for AI coding assistants to navigate. The conventions matter:
1. Read `doc/INDEX.md` to understand what's available
2. Identify which docs match your task (use `keywords.md` if unsure)
3. Load ONLY the relevant docs
4. Do NOT load everything -- context window is precious
1. Read [INDEX.md](INDEX.md) first to understand what's available.
2. Identify which docs match your task — [keywords.md](keywords.md) maps topics to files.
3. Load **only** the relevant docs; the codebase is large.
4. The `Quick Reference` section at the top of every API doc is the highest-signal starting point.
### Loading patterns
```
Task: "Parse a song" → Load: doc/api/song.md
Task: "Fix protobuf parsing" → Load: doc/formats/pp_song_spec.md
Task: "Create a playlist" → Load: doc/api/playlist.md
Task: "Debug ZIP issues" → Load: doc/formats/pp_playlist_spec.md + doc/internal/issues.md
Task: "Add new feature" → Load: relevant api/ doc + doc/CONTRIBUTING.md
```
| Task | Load |
|------|------|
| Parse a song | [api/song.md](api/song.md) |
| Fix protobuf parsing | [formats/pp_song_spec.md](formats/pp_song_spec.md) |
| Create a playlist | [api/playlist.md](api/playlist.md) |
| Debug ZIP issues | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) + [internal/issues.md](internal/issues.md) |
| Add a new feature | The relevant `api/*.md` + this file |

View file

@ -1,6 +1,8 @@
# ProPresenter Parser Documentation
# ProPresenter 7 PHP API — Documentation
> **For AI Agents**: Load only the documents you need. Use the keyword index to find relevant sections.
> Comprehensive reference for the [ProPresenter 7 PHP API](../README.md) library.
>
> Each document is self-contained — open the one that matches your task. If you don't know which to load, search [keywords.md](keywords.md) or scan the table below.
## Quick Navigation
@ -111,119 +113,44 @@ doc/
---
## When to Load What
## What to Read for Each Task
### Task: "Parse a song file"
```
Load: doc/api/song.md
```
### Task: "Generate a new playlist"
```
Load: doc/api/playlist.md
```
### Task: "Read/write a .probundle"
```
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)
```
### Task: "Understand translation handling"
```
Load: doc/api/song.md (section: Translations)
Load: doc/formats/pp_song_spec.md (section 7: Translations)
```
### Task: "Fix ZIP64 issues"
```
Load: doc/formats/pp_playlist_spec.md (section 4: ZIP64 Container Format)
Load: doc/formats/pp_bundle_spec.md (section 4: ZIP64 EOCD Quirk)
Load: doc/internal/learnings.md (search: Zip64Fixer)
```
| Task | Read |
|------|------|
| Parse a song file | [api/song.md](api/song.md) |
| Generate a new playlist | [api/playlist.md](api/playlist.md) |
| Read or write a `.probundle` | [api/bundle.md](api/bundle.md) |
| Edit a global library file (Macros, Labels, Groups, …) | [api/<library>.md](api/) |
| Round-trip a theme folder with assets | [api/theme.md](api/theme.md) + [api/bundle.md](api/bundle.md) |
| Debug protobuf parsing issues | [formats/pp_song_spec.md](formats/pp_song_spec.md) §25 |
| Understand translation handling | [api/song.md](api/song.md) (Translations) + [formats/pp_song_spec.md](formats/pp_song_spec.md) §7 |
| Fix ZIP64 issues | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) §4 + [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) §4 |
| Check internal notes / known bugs | [internal/issues.md](internal/issues.md), [internal/learnings.md](internal/learnings.md) |
---
## Project Overview
This project provides PHP tools to parse, modify, and generate ProPresenter 7 files:
For installation, getting started, and a high-level feature tour see the top-level [README](../README.md).
- **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
In short, this library covers:
- **Songs** (`.pro`) — protobuf files with lyrics, groups, slides, arrangements, translations
- **Playlists** (`.proplaylist`) — ZIP64 archives with embedded songs and media
- **Bundles** (`.probundle`) — ZIP archives bundling a single song with its media assets
- **Themes** — folder with a `Theme` protobuf and an `Assets/` directory
- **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
| File | Purpose |
|------|---------|
| `src/Song.php` | Song wrapper (read/modify `.pro` files) |
| `src/ProFileReader.php` | Read `.pro` files |
| `src/ProFileWriter.php` | Write `.pro` files |
| `src/ProFileGenerator.php` | Generate `.pro` files from scratch |
| `src/PlaylistArchive.php` | Playlist wrapper (read/modify `.proplaylist` files) |
| `src/ProPlaylistReader.php` | Read `.proplaylist` files |
| `src/ProPlaylistWriter.php` | Write `.proplaylist` files |
| `src/ProPlaylistGenerator.php` | Generate `.proplaylist` files from scratch |
| `src/PresentationBundle.php` | Bundle wrapper (read/write `.probundle` files) |
| `src/ProBundleReader.php` | Read `.probundle` files |
| `src/ProBundleWriter.php` | Write `.probundle` files |
| `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
Every supported file type ships with an inspector script in [`bin/`](../bin/). Examples:
```bash
# Songs / playlists / bundles
php bin/parse-song.php path/to/song.pro
php bin/parse-song.php path/to/song.pro
php bin/parse-playlist.php path/to/playlist.proplaylist
# Global library files
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
php bin/parse-theme.php path/to/ThemeFolder
php bin/parse-macros.php path/to/Macros
# … and one for every other global library file
```
See the [README](../README.md#cli-tools) for the full list.

View file

@ -1,6 +1,6 @@
# Keyword Index
> Search this file to find which documents to load for specific topics.
> Search this file (Ctrl+F / Cmd+F) to jump to the right document for a topic.
## File Formats