Analyze a file format of a song. ## Spec File: ./Test.pro (file ext are always .pro) - every song contains parts (name group here) (here: Verse 1, Verse 2, Chorus, ...) but could be any name - every group contains 1-x slides - every song contains different arrangements (here normal and test2) that defines the existence and the order of the groups - every slide CAN have another textbox which contains a translated version of the first textbox ## Status 1. [x] Analyse the file structure and find all of the described specs. 2. [x] Test and verify if the definition is correct - there is a `all-songs` directory with lot of examples. 3. [x] Describe the structure for future AI prompts to use these files in `spec/pp_song_spec.md` and describe the usage in the `AGENTS.md` (replace obsolete commands) 4. [x] Write a PHP module (is later used in laravel) in `./php` which can parse a song and let get/set every aspect of structure. Use Objects here (Song, Group, Slide, Arrangement, etc) 5. [x] Create a simple PHP cli tool, which receive a param with a song file and show the structure of the song. ## PHP Module Usage The ProPresenter song parser is available as a PHP module in `./php`. Use it to read, parse, and modify .pro song files. ### Reading a Song ```php use ProPresenter\Parser\ProFileReader; use ProPresenter\Parser\ProFileWriter; $song = ProFileReader::read('path/to/song.pro'); ``` ### Accessing Song Structure ```php // Basic song info 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. $slides = $song->getSlidesForGroup($group); foreach ($slides as $slide) { echo $slide->getPlainText(); // Optional cue label/title echo $slide->getLabel(); if ($slide->hasTranslation()) { echo $slide->getTranslation()->getPlainText(); } // Optional macro action on the cue if ($slide->hasMacro()) { echo $slide->getMacroName(); echo $slide->getMacroUuid(); echo $slide->getMacroCollectionName(); echo $slide->getMacroCollectionUuid(); } // Optional media action on the cue (image/video) if ($slide->hasMedia()) { echo $slide->getMediaUrl(); echo $slide->getMediaUuid(); echo $slide->getMediaFormat(); } } } // Arrangements (different song orderings) 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'], ['text' => 'Line 3', 'macro' => ['name' => 'Lied 1.Folie', 'uuid' => '20C1DFDE-0FB6-49E5-B90C-E6608D427212']], ]], ['name' => 'Media', 'color' => [0.2, 0.2, 0.2, 1.0], 'slides' => [ ['media' => 'file:///Users/me/Pictures/slide.jpg', 'format' => 'JPG', 'label' => 'slide.jpg'], ['media' => 'file:///Users/me/Pictures/slide2.jpg', 'format' => 'JPG', 'label' => 'slide2.jpg', 'macro' => ['name' => '1:1 - Beamer & Stream', 'uuid' => 'A5911D80-622E-4AD6-A242-9278D0640048']], ]], ['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); ``` ### Edit Label/Macro/Media Data ```php $slide = $song->getSlides()[0]; // Label (Cue.name) $slide->setLabel('Seniorennachmittag März.jpg'); // Macro action $slide->setMacro( 'Lied 1.Folie', '20C1DFDE-0FB6-49E5-B90C-E6608D427212', '--MAIN--', '8D02FC57-83F8-4042-9B90-81C229728426', ); // Remove macro action $slide->removeMacro(); // Read media action if ($slide->hasMedia()) { $url = $slide->getMediaUrl(); $format = $slide->getMediaFormat(); } ``` ## CLI Tool Parse and display song structure from the command line: ```bash php php/bin/parse-song.php path/to/song.pro ``` ## Format Specification For detailed information about the .pro file format, see `spec/pp_song_spec.md`. ## Key Files - `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 - `php/src/Arrangement.php` — Arrangement wrapper - `php/src/RtfExtractor.php` — RTF to plain text converter - `php/src/ProFileReader.php` — Reads .pro files - `php/src/ProFileWriter.php` — Writes .pro files - `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