From 5ac27d676c0e7c1edaf49211c874fb9bd03279f9 Mon Sep 17 00:00:00 2001 From: Thorsten Bus Date: Sun, 3 May 2026 21:59:39 +0200 Subject: [PATCH] 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). --- .gitignore | 28 ++- LICENSE | 29 +++ README.md | 523 ++++++++++++++++++++++++++++++++++++++++++++ composer.json | 45 +++- composer.lock | 2 +- doc/CONTRIBUTING.md | 38 ++-- doc/INDEX.md | 135 +++--------- doc/keywords.md | 2 +- 8 files changed, 673 insertions(+), 129 deletions(-) create mode 100644 LICENSE diff --git a/.gitignore b/.gitignore index dc1805c..e6759a6 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..493a7b6 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index e69de29..cf5fdc6 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/composer.json b/composer.json index ef23fcb..4a0ae70 100644 --- a/composer.json +++ b/composer.json @@ -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 } } diff --git a/composer.lock b/composer.lock index a40e01b..c46e703 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md index 1a0388c..546620d 100644 --- a/doc/CONTRIBUTING.md +++ b/doc/CONTRIBUTING.md @@ -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 | diff --git a/doc/INDEX.md b/doc/INDEX.md index 153a221..4354a15 100644 --- a/doc/INDEX.md +++ b/doc/INDEX.md @@ -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/.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/.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) §2–5 | +| 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. diff --git a/doc/keywords.md b/doc/keywords.md index 230842f..ab41560 100644 --- a/doc/keywords.md +++ b/doc/keywords.md @@ -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