diff --git a/AGENTS.md b/AGENTS.md index 8b762cd..5bf3af8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,19 @@ $song = ProFileReader::read('path/to/song.pro'); echo $song->getName(); // Song name echo $song->getUuid(); // Song UUID +// CCLI metadata +echo $song->getCcliAuthor(); // "Joel Houston, Matt Crocker" +echo $song->getCcliSongTitle(); // "Oceans (Where Feet May Fail)" +echo $song->getCcliPublisher(); // "2012 Hillsong Music Publishing" +echo $song->getCcliCopyrightYear(); // 2012 +echo $song->getCcliSongNumber(); // 6428767 +echo $song->getCcliDisplay(); // true + +// Other metadata +echo $song->getCategory(); // "" +echo $song->getNotes(); // "" +echo $song->getSelectedArrangementUuid(); // "uuid-string" + // Groups (song parts like Verse 1, Chorus, etc.) foreach ($song->getGroups() as $group) { echo $group->getName(); // "Verse 1", "Chorus", etc. @@ -54,15 +67,44 @@ foreach ($song->getArrangements() as $arr) { $groups = $song->getGroupsForArrangement($arr); // Groups in arrangement order } -``` ### Modifying and Writing ```php $song->setName("New Name"); +$song->setCcliAuthor("Author Name"); +$song->setCcliSongNumber(12345); +$song->setCategory("Worship"); +$song->setNotes("Use acoustic intro"); ProFileWriter::write($song, 'output.pro'); ``` +### Generating a New Song + +```php +use ProPresenter\Parser\ProFileGenerator; + +$song = ProFileGenerator::generate( + 'Song Name', + [ + ['name' => 'Verse 1', 'color' => [0.13, 0.59, 0.95, 1.0], 'slides' => [ + ['text' => 'Line 1'], + ['text' => 'Line 2', 'translation' => 'Zeile 2'], + ]], + ['name' => 'Chorus', 'color' => [0.95, 0.27, 0.27, 1.0], 'slides' => [ + ['text' => 'Chorus text'], + ]], + ], + [ + ['name' => 'normal', 'groupNames' => ['Verse 1', 'Chorus', 'Verse 1']], + ], + ['author' => 'Author', 'song_title' => 'Title', 'copyright_year' => 2024], +); + +// Or generate and write in one call +ProFileGenerator::generateAndWrite('output.pro', 'Song Name', $groups, $arrangements, $ccli); +``` + ## CLI Tool Parse and display song structure from the command line: @@ -77,7 +119,7 @@ For detailed information about the .pro file format, see `spec/pp_song_spec.md`. ## Key Files -- `php/src/Song.php` — Top-level song wrapper +- `php/src/Song.php` — Top-level song wrapper (metadata, CCLI, groups, slides, arrangements) - `php/src/Group.php` — Group (song part) wrapper - `php/src/Slide.php` — Slide wrapper with text access - `php/src/TextElement.php` — Text element with label + plain text @@ -85,5 +127,6 @@ For detailed information about the .pro file format, see `spec/pp_song_spec.md`. - `php/src/RtfExtractor.php` — RTF to plain text converter - `php/src/ProFileReader.php` — Reads .pro files - `php/src/ProFileWriter.php` — Writes .pro files -- `php/bin/parse-song.php` — CLI tool +- `php/src/ProFileGenerator.php` — Generates .pro files from scratch +- `php/bin/parse-song.php` — CLI tool (shows metadata, groups, slides, arrangements) - `spec/pp_song_spec.md` — Format specification diff --git a/spec/pp_song_spec.md b/spec/pp_song_spec.md index de1573a..3fdb6f5 100644 --- a/spec/pp_song_spec.md +++ b/spec/pp_song_spec.md @@ -1,6 +1,6 @@ # ProPresenter 7 `.pro` File Format Specification -**Version:** 1.0 +**Version:** 1.1 **Target Audience:** AI agents, automated parsers, developers **Proto Source:** greyshirtguy/ProPresenter7-Proto v7.16.2 (MIT License) @@ -94,12 +94,38 @@ Presentation | Field Path | Protobuf Type | Field Number | Description | |------------|---------------|--------------|-------------| -| `name` | `string` | 1 | Song title (e.g., "Amazing Grace") | -| `uuid` | `rv.data.UUID` | 5 | Unique identifier for the presentation | -| `cues[]` | `rv.data.Cue` | 13 | Array of slides | -| `cue_groups[]` | `rv.data.Presentation.CueGroup` | 12 | Array of groups (song parts) | +| `application_info` | `rv.data.ApplicationInfo` | 1 | Platform and application version info | +| `uuid` | `rv.data.UUID` | 2 | Unique identifier for the presentation | +| `name` | `string` | 3 | Song title (e.g., "Amazing Grace") | +| `last_date_used` | `rv.data.Timestamp` | 4 | Last date the song was used | +| `last_modified_date` | `rv.data.Timestamp` | 5 | Last modification date | +| `category` | `string` | 6 | Optional category label | +| `notes` | `string` | 7 | Optional notes | +| `background` | `rv.data.Background` | 8 | Background color/image | +| `selected_arrangement` | `rv.data.UUID` | 10 | UUID of the currently selected arrangement | | `arrangements[]` | `rv.data.Presentation.Arrangement` | 11 | Array of arrangements | +| `cue_groups[]` | `rv.data.Presentation.CueGroup` | 12 | Array of groups (song parts) | +| `cues[]` | `rv.data.Cue` | 13 | Array of slides | +| `ccli` | `rv.data.Presentation.CCLI` | 14 | CCLI licensing metadata | +| `timeline` | `rv.data.Presentation.Timeline` | 17 | Timeline with duration | +| `music_key` | `string` | 22 | Music key (rarely used) | +| `music` | `rv.data.Presentation.Music` | 23 | Music key scale data | +### Presentation.CCLI + +CCLI (Christian Copyright Licensing International) metadata. Present in 157 out of 168 reference files. + +| Field Path | Protobuf Type | Field Number | Description | +|------------|---------------|--------------|-------------| +| `author` | `string` | 1 | Song author(s) (e.g., "Joel Houston, Matt Crocker") | +| `artist_credits` | `string` | 2 | Artist credits (rarely used) | +| `song_title` | `string` | 3 | CCLI song title | +| `publisher` | `string` | 4 | Publisher (e.g., "2012 Hillsong Music Publishing") | +| `copyright_year` | `uint32` | 5 | Copyright year (e.g., 2012) | +| `song_number` | `uint32` | 6 | CCLI song number (e.g., 6428767) | +| `display` | `bool` | 7 | Whether to display CCLI info | +| `album` | `string` | 8 | Album name (rarely used) | +| `artwork` | `bytes` | 9 | Album artwork (rarely used) | ### Presentation.CueGroup | Field Path | Protobuf Type | Field Number | Description | @@ -416,19 +442,28 @@ $song = ProFileReader::read('path/to/song.pro'); ### Access Song Metadata ```php -// Song name +// Song name and UUID $name = $song->getName(); // "Amazing Grace" - -// Song UUID $uuid = $song->getUuid(); // "A1B2C3D4-..." -// Groups -$groups = $song->getGroups(); // Group[] +// CCLI metadata +$author = $song->getCcliAuthor(); // "Joel Houston, Matt Crocker" +$title = $song->getCcliSongTitle(); // "Oceans (Where Feet May Fail)" +$publisher = $song->getCcliPublisher(); // "2012 Hillsong Music Publishing" +$year = $song->getCcliCopyrightYear(); // 2012 +$number = $song->getCcliSongNumber(); // 6428767 +$display = $song->getCcliDisplay(); // true +$credits = $song->getCcliArtistCredits(); // "" +$album = $song->getCcliAlbum(); // "" -// Slides -$slides = $song->getSlides(); // Slide[] +// Other metadata +$category = $song->getCategory(); // "" +$notes = $song->getNotes(); // "" +$selectedArr = $song->getSelectedArrangementUuid(); // "uuid-string" -// Arrangements +// Groups, Slides, Arrangements +$groups = $song->getGroups(); // Group[] +$slides = $song->getSlides(); // Slide[] $arrangements = $song->getArrangements(); // Arrangement[] ``` @@ -491,17 +526,23 @@ foreach ($slides as $slide) { ```php use ProPresenter\Parser\ProFileWriter; -// Modify song +// Modify song metadata $song->setName("New Song Title"); +$song->setCategory("Worship"); +$song->setNotes("Use acoustic intro"); + +// Modify CCLI metadata +$song->setCcliAuthor("Author Name"); +$song->setCcliSongTitle("Song Title"); +$song->setCcliPublisher("Publisher"); +$song->setCcliCopyrightYear(2024); +$song->setCcliSongNumber(12345); +$song->setCcliDisplay(true); // Modify group $group = $song->getGroupByName("Verse 1"); $group->setName("Strophe 1"); -// Modify arrangement -$arrangement = $song->getArrangementByName("normal"); -$arrangement->setName("default"); - // Write to file ProFileWriter::write($song, 'output.pro'); ``` @@ -559,26 +600,84 @@ $song->getPresentation()->getArrangements()[] = $arrangement->getProto(); ProFileWriter::write($song, 'output.pro'); ``` +### Generate a New Song +```php +use ProPresenter\Parser\ProFileGenerator; + +$song = ProFileGenerator::generate( + 'Amazing Grace', + [ + [ + 'name' => 'Verse 1', + 'color' => [0.13, 0.59, 0.95, 1.0], + 'slides' => [ + ['text' => 'Amazing grace, how sweet the sound'], + ['text' => 'That saved a wretch like me'], + ], + ], + [ + 'name' => 'Chorus', + 'color' => [0.95, 0.27, 0.27, 1.0], + 'slides' => [ + ['text' => 'I once was lost, but now am found'], + ], + ], + ], + [ + ['name' => 'normal', 'groupNames' => ['Verse 1', 'Chorus', 'Verse 1']], + ], + [ + 'author' => 'John Newton', + 'song_title' => 'Amazing Grace', + 'copyright_year' => 1779, + ], +); + +// Write to file +ProFileGenerator::generateAndWrite('output.pro', 'Amazing Grace', $groups, $arrangements, $ccli); +``` + +### Generate a Song with Translations +```php +$song = ProFileGenerator::generate( + 'Oceans', + [ + [ + 'name' => 'Verse 1', + 'color' => [0.13, 0.59, 0.95, 1.0], + 'slides' => [ + [ + 'text' => 'You call me out upon the waters', + 'translation' => 'Du rufst mich auf das Wasser', + ], + ], + ], + ], + [ + ['name' => 'normal', 'groupNames' => ['Verse 1']], + ], +); +``` + --- ## Appendix: Test.pro Structure ### Groups (4) -1. **Verse 1** → 1 slide +1. **Verse 1** → 2 slides 2. **Verse 2** → 1 slide -3. **Chorus** → 2 slides +3. **Chorus** → 1 slide 4. **Ending** → 1 slide ### Slides (5) -- Slide 1: Verse 1 text (2 text elements: "Orginal", "Deutsch") -- Slide 2: Verse 2 text (2 text elements) -- Slide 3: Chorus text part 1 (2 text elements) -- Slide 4: Chorus text part 2 (2 text elements) -- Slide 5: Ending text (2 text elements) +- Slides 1-2: Verse 1 text (2 text elements each: "Orginal", "Deutsch") +- Slide 3: Verse 2 text (2 text elements) +- Slide 4: Chorus text (2 text elements) +- Slide 5: Ending text (2 text elements, with translations) ### Arrangements (2) -1. **normal:** Verse 1 → Chorus → Verse 2 → Chorus → Ending -2. **test2:** Verse 1 → Verse 2 → Chorus +1. **normal:** Chorus → Verse 1 → Chorus → Verse 2 → Chorus +2. **test2:** Verse 1 → Chorus → Verse 2 → Chorus --- @@ -588,6 +687,7 @@ ProFileWriter::write($song, 'output.pro'); - **Parseable Files:** 168 - **Empty Files:** 1 (invalid) - **Files Without Arrangements:** 17 (valid) +- **Files With CCLI Data:** 157 out of 168 - **Binary Fidelity:** 0 files pass round-trip decode→encode (proto definitions incomplete) --- @@ -596,11 +696,30 @@ ProFileWriter::write($song, 'output.pro'); | Message | Field | Number | |---------|-------|--------| -| Presentation | name | 1 | -| Presentation | uuid | 5 | -| Presentation | cues | 13 | -| Presentation | cue_groups | 12 | +| Presentation | application_info | 1 | +| Presentation | uuid | 2 | +| Presentation | name | 3 | +| Presentation | last_date_used | 4 | +| Presentation | last_modified_date | 5 | +| Presentation | category | 6 | +| Presentation | notes | 7 | +| Presentation | selected_arrangement | 10 | | Presentation | arrangements | 11 | +| Presentation | cue_groups | 12 | +| Presentation | cues | 13 | +| Presentation | ccli | 14 | +| Presentation | timeline | 17 | +| Presentation | music_key | 22 | +| Presentation | music | 23 | +| Presentation.CCLI | author | 1 | +| Presentation.CCLI | artist_credits | 2 | +| Presentation.CCLI | song_title | 3 | +| Presentation.CCLI | publisher | 4 | +| Presentation.CCLI | copyright_year | 5 | +| Presentation.CCLI | song_number | 6 | +| Presentation.CCLI | display | 7 | +| Presentation.CCLI | album | 8 | +| Presentation.CCLI | artwork | 9 | | CueGroup | group | 1 | | CueGroup | cue_identifiers | 2 | | Group | uuid | 1 |