From 3a33597bcfb55bb29d2620eab197dcbae3e2a0f7 Mon Sep 17 00:00:00 2001 From: Thorsten Bus Date: Sun, 1 Mar 2026 18:46:08 +0100 Subject: [PATCH] [AI] add slide label, macro, and media support --- php/src/Slide.php | 154 ++++++++++++++++++++++++- php/tests/SlideExtendedTest.php | 198 ++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 php/tests/SlideExtendedTest.php diff --git a/php/src/Slide.php b/php/src/Slide.php index 9734ef9..d309af6 100644 --- a/php/src/Slide.php +++ b/php/src/Slide.php @@ -4,7 +4,12 @@ declare(strict_types=1); namespace ProPresenter\Parser; +use Rv\Data\Action; +use Rv\Data\Action\ActionType; +use Rv\Data\Action\MacroType; +use Rv\Data\CollectionElementType; use Rv\Data\Cue; +use Rv\Data\UUID; /** * Read wrapper around a protobuf Cue representing a slide. @@ -126,6 +131,122 @@ class Slide $textElements[1]->setPlainText($text); } + public function getLabel(): string + { + return $this->cue->getName(); + } + + public function setLabel(string $label): void + { + $this->cue->setName($label); + } + + public function hasMacro(): bool + { + return $this->findMacroAction() !== null; + } + + public function getMacroName(): ?string + { + $macro = $this->findMacroAction(); + return $macro?->getMacro()?->getIdentification()?->getParameterName(); + } + + public function getMacroUuid(): ?string + { + $macro = $this->findMacroAction(); + return $macro?->getMacro()?->getIdentification()?->getParameterUuid()?->getString(); + } + + public function getMacroCollectionName(): ?string + { + $macro = $this->findMacroAction(); + return $macro?->getMacro()?->getIdentification()?->getParentCollection()?->getParameterName(); + } + + public function getMacroCollectionUuid(): ?string + { + $macro = $this->findMacroAction(); + return $macro?->getMacro()?->getIdentification()?->getParentCollection()?->getParameterUuid()?->getString(); + } + + public function setMacro(string $name, string $uuid, string $collectionName = '--MAIN--', string $collectionUuid = ''): void + { + $parentCollectionUuid = new UUID(); + $parentCollectionUuid->setString($collectionUuid); + + $parentCollection = new CollectionElementType(); + $parentCollection->setParameterName($collectionName); + $parentCollection->setParameterUuid($parentCollectionUuid); + + $macroUuid = new UUID(); + $macroUuid->setString($uuid); + + $identification = new CollectionElementType(); + $identification->setParameterName($name); + $identification->setParameterUuid($macroUuid); + $identification->setParentCollection($parentCollection); + + $macroType = new MacroType(); + $macroType->setIdentification($identification); + + $existingMacroAction = $this->findMacroAction(); + if ($existingMacroAction !== null) { + $existingMacroAction->setType(ActionType::ACTION_TYPE_MACRO); + $existingMacroAction->setMacro($macroType); + $existingMacroAction->setIsEnabled(true); + return; + } + + $macroAction = new Action(); + $macroAction->setUuid(new UUID()); + $macroAction->setType(ActionType::ACTION_TYPE_MACRO); + $macroAction->setMacro($macroType); + $macroAction->setIsEnabled(true); + + $actions = []; + foreach ($this->cue->getActions() as $action) { + $actions[] = $action; + } + $actions[] = $macroAction; + $this->cue->setActions($actions); + } + + public function removeMacro(): void + { + $filteredActions = []; + foreach ($this->cue->getActions() as $action) { + if ($action->getType() !== ActionType::ACTION_TYPE_MACRO) { + $filteredActions[] = $action; + } + } + + $this->cue->setActions($filteredActions); + } + + public function hasMedia(): bool + { + return $this->findMediaAction() !== null; + } + + public function getMediaUrl(): ?string + { + $media = $this->findMediaAction(); + return $media?->getMedia()?->getElement()?->getUrl()?->getAbsoluteString(); + } + + public function getMediaUuid(): ?string + { + $media = $this->findMediaAction(); + return $media?->getMedia()?->getElement()?->getUuid()?->getString(); + } + + public function getMediaFormat(): ?string + { + $media = $this->findMediaAction(); + return $media?->getMedia()?->getElement()?->getMetadata()?->getFormat(); + } + /** * Access the underlying protobuf Cue. */ @@ -143,12 +264,17 @@ class Slide */ private function getSlideElements(): iterable { - $actions = $this->cue->getActions(); - if (count($actions) === 0) { + $firstAction = null; + foreach ($this->cue->getActions() as $action) { + $firstAction = $action; + break; + } + + if ($firstAction === null) { return []; } - $slideType = $actions[0]->getSlide(); + $slideType = $firstAction->getSlide(); if ($slideType === null) { return []; } @@ -165,4 +291,26 @@ class Slide return $baseSlide->getElements(); } + + private function findMacroAction(): ?Action + { + foreach ($this->cue->getActions() as $action) { + if ($action->getType() === ActionType::ACTION_TYPE_MACRO) { + return $action; + } + } + + return null; + } + + private function findMediaAction(): ?Action + { + foreach ($this->cue->getActions() as $action) { + if ($action->getType() === ActionType::ACTION_TYPE_MEDIA) { + return $action; + } + } + + return null; + } } diff --git a/php/tests/SlideExtendedTest.php b/php/tests/SlideExtendedTest.php new file mode 100644 index 0000000..daffe0f --- /dev/null +++ b/php/tests/SlideExtendedTest.php @@ -0,0 +1,198 @@ +assertTrue($slide->hasMacro()); + } + + #[Test] + public function testMacroNameAndUuid(): void + { + $song = self::readSong(self::TEST_WITH_MACRO); + $slide = self::getSlideByGroupName($song, 'COPYRIGHT', 0); + + $this->assertSame('Lied 1.Folie', $slide->getMacroName()); + $this->assertSame('20C1DFDE-0FB6-49E5-B90C-E6608D427212', $slide->getMacroUuid()); + } + + #[Test] + public function testMacroCollectionNameAndUuid(): void + { + $song = self::readSong(self::TEST_WITH_MACRO); + $slide = self::getSlideByGroupName($song, 'COPYRIGHT', 0); + + $this->assertSame('--MAIN--', $slide->getMacroCollectionName()); + $this->assertSame('8D02FC57-83F8-4042-9B90-81C229728426', $slide->getMacroCollectionUuid()); + } + + #[Test] + public function testRegularSlideHasNoMacro(): void + { + $song = self::readSong(self::TEST_WITH_MACRO); + $slide = self::getSlideByGroupName($song, 'Verse 1', 0); + + $this->assertFalse($slide->hasMacro()); + } + + #[Test] + public function testSetMacroOnSlide(): void + { + $song = self::readSong(self::TEST_WITH_MACRO); + $slide = self::getSlideByGroupName($song, 'Verse 1', 0); + + $slide->setMacro('Macro Name', '11111111-2222-3333-4444-555555555555', 'Collection Name', 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE'); + + $this->assertTrue($slide->hasMacro()); + $this->assertSame('Macro Name', $slide->getMacroName()); + $this->assertSame('11111111-2222-3333-4444-555555555555', $slide->getMacroUuid()); + $this->assertSame('Collection Name', $slide->getMacroCollectionName()); + $this->assertSame('AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE', $slide->getMacroCollectionUuid()); + } + + #[Test] + public function testRemoveMacro(): void + { + $song = self::readSong(self::TEST_WITH_MACRO); + $slide = self::getSlideByGroupName($song, 'Verse 1', 0); + + $slide->setMacro('Macro Name', '11111111-2222-3333-4444-555555555555'); + $this->assertTrue($slide->hasMacro()); + + $slide->removeMacro(); + $this->assertFalse($slide->hasMacro()); + } + + #[Test] + public function testSetAndRemoveMacroRoundTrip(): void + { + $song = self::readSong(self::TEST_WITH_MACRO); + $slide = self::getSlideByGroupName($song, 'Verse 1', 0); + + $slide->setMacro('Round Trip Macro', 'AAAAAAAA-1111-2222-3333-BBBBBBBBBBBB', '--MAIN--', '8D02FC57-83F8-4042-9B90-81C229728426'); + + $tempPath = sys_get_temp_dir() . '/propresenter-slide-extended-' . uniqid('', true) . '.pro'; + try { + ProFileWriter::write($song, $tempPath); + + $readBack = ProFileReader::read($tempPath); + $readBackSlide = self::getSlideByGroupName($readBack, 'Verse 1', 0); + + $this->assertTrue($readBackSlide->hasMacro()); + $this->assertSame('Round Trip Macro', $readBackSlide->getMacroName()); + $this->assertSame('AAAAAAAA-1111-2222-3333-BBBBBBBBBBBB', $readBackSlide->getMacroUuid()); + + $readBackSlide->removeMacro(); + $this->assertFalse($readBackSlide->hasMacro()); + } finally { + @unlink($tempPath); + } + } + + #[Test] + public function testImageSlideHasMedia(): void + { + $song = self::readSong(self::TEST_WITH_MEDIA_AND_MACRO); + $slide = $song->getSlides()[0]; + + $this->assertTrue($slide->hasMedia()); + } + + #[Test] + public function testMediaUrlAndFormat(): void + { + $song = self::readSong(self::TEST_WITH_MEDIA_AND_MACRO); + $slide = $song->getSlides()[0]; + + $this->assertStringContainsString('file://', (string) $slide->getMediaUrl()); + $this->assertSame('JPG', $slide->getMediaFormat()); + } + + #[Test] + public function testImageSlideHasNoText(): void + { + $song = self::readSong(self::TEST_WITH_MEDIA_AND_MACRO); + $slide = $song->getSlides()[0]; + + $this->assertSame('', $slide->getPlainText()); + } + + #[Test] + public function testSlideWithLabel(): void + { + $song = self::readSong(self::TEST_WITH_MEDIA_AND_MACRO); + $slide = $song->getSlides()[1]; + + $this->assertSame('Seniorennachmittag März.jpg', $slide->getLabel()); + } + + #[Test] + public function testSlideWithoutLabel(): void + { + $song = self::readSong(self::TEST_WITH_MEDIA_AND_MACRO); + $slide = $song->getSlides()[0]; + + $this->assertSame('', $slide->getLabel()); + } + + #[Test] + public function testImageSlideWithMacro(): void + { + $song = self::readSong(self::TEST_WITH_MEDIA_AND_MACRO); + $slide = $song->getSlides()[1]; + + $this->assertTrue($slide->hasMacro()); + $this->assertSame('1:1 - Beamer & Stream', $slide->getMacroName()); + } + + #[Test] + public function testSetLabel(): void + { + $song = self::readSong(self::TEST_WITH_MEDIA_AND_MACRO); + $slide = $song->getSlides()[1]; + + $slide->setLabel('New Label'); + + $this->assertSame('New Label', $slide->getLabel()); + } + + private static function readSong(string $path): Song + { + if (!file_exists($path)) { + self::markTestSkipped('Reference file not found: ' . $path); + } + + return ProFileReader::read($path); + } + + private static function getSlideByGroupName(Song $song, string $groupName, int $slideIndex): Slide + { + $group = $song->getGroupByName($groupName); + self::assertInstanceOf(Group::class, $group, 'Group not found: ' . $groupName); + + $slides = $song->getSlidesForGroup($group); + self::assertArrayHasKey($slideIndex, $slides, 'Slide index not found in group: ' . $groupName); + + return $slides[$slideIndex]; + } +}