diff --git a/src/ProFileGenerator.php b/src/ProFileGenerator.php index d2b462d..85b343b 100644 --- a/src/ProFileGenerator.php +++ b/src/ProFileGenerator.php @@ -15,8 +15,9 @@ use Rv\Data\AlphaType; use Rv\Data\ApplicationInfo; use Rv\Data\ApplicationInfo\Application; use Rv\Data\ApplicationInfo\Platform; -use Rv\Data\Color; +use Rv\Data\Background; use Rv\Data\CollectionElementType; +use Rv\Data\Color; use Rv\Data\Cue; use Rv\Data\FileProperties; use Rv\Data\Graphics\EdgeInsets; @@ -47,17 +48,16 @@ use Rv\Data\Presentation; use Rv\Data\Presentation\Arrangement; use Rv\Data\Presentation\CCLI; use Rv\Data\Presentation\CueGroup; +use Rv\Data\Presentation\Timeline; use Rv\Data\PresentationSlide; use Rv\Data\Slide; use Rv\Data\Slide\Element as SlideElement; use Rv\Data\Slide\Element\TextScroller; -use Rv\Data\Background; use Rv\Data\URL; use Rv\Data\URL\LocalRelativePath; use Rv\Data\URL\Platform as UrlPlatform; use Rv\Data\UUID; use Rv\Data\Version; -use Rv\Data\Presentation\Timeline; final class ProFileGenerator { @@ -110,7 +110,7 @@ final class ProFileGenerator $groupIdentifiers = []; foreach ($arrangementData['groupNames'] as $groupName) { - if (!isset($groupUuidsByName[$groupName])) { + if (! isset($groupUuidsByName[$groupName])) { continue; } @@ -180,7 +180,9 @@ final class ProFileGenerator private static function buildCue(string $cueUuid, array $slideData): Cue { $elements = []; - if (isset($slideData['text'])) { + $imageOnly = ($slideData['imageOnly'] ?? false) === true; + + if (! $imageOnly && isset($slideData['text'])) { $hasTranslation = isset($slideData['translation']) && $slideData['translation'] !== null; if ($hasTranslation) { @@ -210,6 +212,10 @@ final class ProFileGenerator $actions = [self::buildSlideAction($slideType)]; + if (isset($slideData['background']) && is_array($slideData['background'])) { + $actions[] = self::buildBackgroundMediaAction($slideData['background']); + } + if (isset($slideData['media'])) { // Derive name from label OR filename without extension $mediaName = $slideData['label'] ?? null; @@ -282,8 +288,28 @@ final class ProFileGenerator return $action; } - private static function buildMediaAction(string $absoluteUrl, string $format, ?string $name = null, int $imageWidth = 0, int $imageHeight = 0, bool $bundleRelative = false): Action + private static function buildBackgroundMediaAction(array $backgroundData): Action { + return self::buildMediaAction( + (string) ($backgroundData['path'] ?? ''), + (string) ($backgroundData['format'] ?? 'JPG'), + null, + (int) ($backgroundData['width'] ?? 1920), + (int) ($backgroundData['height'] ?? 1080), + (bool) ($backgroundData['bundleRelative'] ?? false), + LayerType::LAYER_TYPE_BACKGROUND, + ); + } + + private static function buildMediaAction( + string $absoluteUrl, + string $format, + ?string $name = null, + int $imageWidth = 0, + int $imageHeight = 0, + bool $bundleRelative = false, + int $layerType = LayerType::LAYER_TYPE_FOREGROUND, + ): Action { if ($bundleRelative) { $filename = basename($absoluteUrl); $url = self::buildBundleRelativeUrl($filename); @@ -335,7 +361,7 @@ final class ProFileGenerator $mediaElement->setImage($imageTypeProperties); $mediaType = new MediaType(); - $mediaType->setLayerType(LayerType::LAYER_TYPE_FOREGROUND); + $mediaType->setLayerType($layerType); $mediaType->setElement($mediaElement); $mediaType->setAudio(new Audio()); @@ -558,6 +584,7 @@ final class ProFileGenerator $color->setAlpha(1.0); $background = new Background(); $background->setColor($color); + return $background; } @@ -565,6 +592,7 @@ final class ProFileGenerator { $url = new URL(); $url->setPlatform(UrlPlatform::PLATFORM_MACOS); + return $url; } @@ -572,6 +600,7 @@ final class ProFileGenerator { $timeline = new Timeline(); $timeline->setDuration(300.0); + return $timeline; } @@ -694,8 +723,8 @@ RTF); private static function newUuidString(): string { $bytes = random_bytes(16); - $bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40); - $bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80); + $bytes[6] = chr((ord($bytes[6]) & 0x0F) | 0x40); + $bytes[8] = chr((ord($bytes[8]) & 0x3F) | 0x80); $hex = bin2hex($bytes); return strtoupper(sprintf( diff --git a/src/Slide.php b/src/Slide.php index d309af6..d25adb9 100644 --- a/src/Slide.php +++ b/src/Slide.php @@ -6,6 +6,7 @@ namespace ProPresenter\Parser; use Rv\Data\Action; use Rv\Data\Action\ActionType; +use Rv\Data\Action\LayerType; use Rv\Data\Action\MacroType; use Rv\Data\CollectionElementType; use Rv\Data\Cue; @@ -97,7 +98,7 @@ class Slide public function setPlainText(string $text): void { $textElements = $this->getTextElements(); - if (!isset($textElements[0])) { + if (! isset($textElements[0])) { return; } @@ -118,13 +119,14 @@ class Slide public function getTranslation(): ?TextElement { $textElements = $this->getTextElements(); + return $textElements[1] ?? null; } public function setTranslation(string $text): void { $textElements = $this->getTextElements(); - if (!isset($textElements[1])) { + if (! isset($textElements[1])) { return; } @@ -149,24 +151,28 @@ class Slide 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(); } @@ -195,6 +201,7 @@ class Slide $existingMacroAction->setType(ActionType::ACTION_TYPE_MACRO); $existingMacroAction->setMacro($macroType); $existingMacroAction->setIsEnabled(true); + return; } @@ -232,18 +239,40 @@ class Slide 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(); + } + + public function hasBackgroundMedia(): bool + { + return $this->findBackgroundMediaAction() !== null; + } + + public function getBackgroundMediaUrl(): ?string + { + $media = $this->findBackgroundMediaAction(); + + return $media?->getMedia()?->getElement()?->getUrl()?->getAbsoluteString(); + } + + public function getBackgroundMediaFormat(): ?string + { + $media = $this->findBackgroundMediaAction(); + return $media?->getMedia()?->getElement()?->getMetadata()?->getFormat(); } @@ -304,9 +333,19 @@ class Slide } private function findMediaAction(): ?Action + { + return $this->findMediaActionByLayerType(LayerType::LAYER_TYPE_FOREGROUND); + } + + private function findBackgroundMediaAction(): ?Action + { + return $this->findMediaActionByLayerType(LayerType::LAYER_TYPE_BACKGROUND); + } + + private function findMediaActionByLayerType(int $layerType): ?Action { foreach ($this->cue->getActions() as $action) { - if ($action->getType() === ActionType::ACTION_TYPE_MEDIA) { + if ($action->getType() === ActionType::ACTION_TYPE_MEDIA && $action->getMedia()?->getLayerType() === $layerType) { return $action; } } diff --git a/tests/BackgroundLayerTest.php b/tests/BackgroundLayerTest.php new file mode 100644 index 0000000..fbe521e --- /dev/null +++ b/tests/BackgroundLayerTest.php @@ -0,0 +1,140 @@ + 'Verse 1', + 'color' => [0.1, 0.2, 0.3, 1.0], + 'slides' => [[ + 'text' => 'Text auf Folie', + 'background' => [ + 'path' => 'file:///tmp/bg.jpg', + 'format' => 'JPG', + 'width' => 1920, + 'height' => 1080, + ], + ]], + ]], + [['name' => 'normal', 'groupNames' => ['Verse 1']]], + ); + + $slide = $song->getSlides()[0]; + $mediaActions = self::mediaActions($slide->getCue()->getActions()); + + $this->assertCount(1, $slide->getTextElements()); + $this->assertSame('Text auf Folie', $slide->getPlainText()); + $this->assertCount(1, $mediaActions); + $this->assertSame(LayerType::LAYER_TYPE_BACKGROUND, $mediaActions[0]->getMedia()->getLayerType()); + $this->assertSame('file:///tmp/bg.jpg', $mediaActions[0]->getMedia()->getElement()->getUrl()->getAbsoluteString()); + $this->assertSame('JPG', $mediaActions[0]->getMedia()->getElement()->getMetadata()->getFormat()); + $this->assertSame(1920.0, $mediaActions[0]->getMedia()->getElement()->getImage()->getDrawing()->getNaturalSize()->getWidth()); + $this->assertSame(1080.0, $mediaActions[0]->getMedia()->getElement()->getImage()->getDrawing()->getNaturalSize()->getHeight()); + $this->assertTrue($slide->hasBackgroundMedia()); + $this->assertSame('file:///tmp/bg.jpg', $slide->getBackgroundMediaUrl()); + $this->assertSame('JPG', $slide->getBackgroundMediaFormat()); + } + + #[Test] + public function generated_image_only_slide_has_no_text_elements(): void + { + $song = ProFileGenerator::generate( + 'Image Only Background Song', + [[ + 'name' => 'Image', + 'color' => [0.1, 0.2, 0.3, 1.0], + 'slides' => [[ + 'text' => 'Dieser Text darf nicht erscheinen', + 'imageOnly' => true, + 'background' => [ + 'path' => 'file:///tmp/image-only.jpg', + 'format' => 'JPG', + 'width' => 1920, + 'height' => 1080, + ], + ]], + ]], + [['name' => 'normal', 'groupNames' => ['Image']]], + ); + + $slide = $song->getSlides()[0]; + $mediaActions = self::mediaActions($slide->getCue()->getActions()); + + $this->assertCount(0, $slide->getTextElements()); + $this->assertSame('', $slide->getPlainText()); + $this->assertCount(1, $mediaActions); + $this->assertSame(LayerType::LAYER_TYPE_BACKGROUND, $mediaActions[0]->getMedia()->getLayerType()); + $this->assertTrue($slide->hasBackgroundMedia()); + $this->assertSame('file:///tmp/image-only.jpg', $slide->getBackgroundMediaUrl()); + } + + #[Test] + public function generated_slide_can_have_background_and_foreground_media_in_order(): void + { + $song = ProFileGenerator::generate( + 'Mixed Media Song', + [[ + 'name' => 'Verse 1', + 'color' => [0.1, 0.2, 0.3, 1.0], + 'slides' => [[ + 'text' => 'Text vor Bild', + 'background' => [ + 'path' => 'file:///tmp/background.jpg', + 'format' => 'JPG', + 'width' => 1920, + 'height' => 1080, + ], + 'media' => 'file:///tmp/foreground.png', + 'format' => 'PNG', + 'mediaWidth' => 640, + 'mediaHeight' => 360, + ]], + ]], + [['name' => 'normal', 'groupNames' => ['Verse 1']]], + ); + + $slide = $song->getSlides()[0]; + $mediaActions = self::mediaActions($slide->getCue()->getActions()); + + $this->assertCount(2, $mediaActions); + $this->assertSame(LayerType::LAYER_TYPE_BACKGROUND, $mediaActions[0]->getMedia()->getLayerType()); + $this->assertSame(LayerType::LAYER_TYPE_FOREGROUND, $mediaActions[1]->getMedia()->getLayerType()); + $this->assertSame('file:///tmp/background.jpg', $mediaActions[0]->getMedia()->getElement()->getUrl()->getAbsoluteString()); + $this->assertSame('file:///tmp/foreground.png', $mediaActions[1]->getMedia()->getElement()->getUrl()->getAbsoluteString()); + $this->assertTrue($slide->hasBackgroundMedia()); + $this->assertTrue($slide->hasMedia()); + $this->assertSame('file:///tmp/background.jpg', $slide->getBackgroundMediaUrl()); + $this->assertSame('file:///tmp/foreground.png', $slide->getMediaUrl()); + } + + /** + * @param iterable $actions + * @return Action[] + */ + private static function mediaActions(iterable $actions): array + { + $mediaActions = []; + foreach ($actions as $action) { + if ($action->getType() === ActionType::ACTION_TYPE_MEDIA) { + $mediaActions[] = $action; + } + } + + return $mediaActions; + } +}