[AI] add slide label, macro, and media support

This commit is contained in:
Thorsten Bus 2026-03-01 18:46:08 +01:00
parent addda54957
commit 3a33597bcf
2 changed files with 349 additions and 3 deletions

View file

@ -4,7 +4,12 @@ declare(strict_types=1);
namespace ProPresenter\Parser; 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\Cue;
use Rv\Data\UUID;
/** /**
* Read wrapper around a protobuf Cue representing a slide. * Read wrapper around a protobuf Cue representing a slide.
@ -126,6 +131,122 @@ class Slide
$textElements[1]->setPlainText($text); $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. * Access the underlying protobuf Cue.
*/ */
@ -143,12 +264,17 @@ class Slide
*/ */
private function getSlideElements(): iterable private function getSlideElements(): iterable
{ {
$actions = $this->cue->getActions(); $firstAction = null;
if (count($actions) === 0) { foreach ($this->cue->getActions() as $action) {
$firstAction = $action;
break;
}
if ($firstAction === null) {
return []; return [];
} }
$slideType = $actions[0]->getSlide(); $slideType = $firstAction->getSlide();
if ($slideType === null) { if ($slideType === null) {
return []; return [];
} }
@ -165,4 +291,26 @@ class Slide
return $baseSlide->getElements(); 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;
}
} }

View file

@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace ProPresenter\Parser\Tests;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use ProPresenter\Parser\Group;
use ProPresenter\Parser\ProFileReader;
use ProPresenter\Parser\ProFileWriter;
use ProPresenter\Parser\Slide;
use ProPresenter\Parser\Song;
class SlideExtendedTest extends TestCase
{
private const TEST_WITH_MACRO = '/Users/thorsten/AI/propresenter-work/ref/TestMitMakro.pro';
private const TEST_WITH_MEDIA_AND_MACRO = '/Users/thorsten/AI/propresenter-work/ref/TestMitBildernUndMakro.pro';
#[Test]
public function testCopyrightSlideHasMacro(): void
{
$song = self::readSong(self::TEST_WITH_MACRO);
$slide = self::getSlideByGroupName($song, 'COPYRIGHT', 0);
$this->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];
}
}