diff --git a/src/ProFileGenerator.php b/src/ProFileGenerator.php index 9294897..34112a2 100644 --- a/src/ProFileGenerator.php +++ b/src/ProFileGenerator.php @@ -19,6 +19,9 @@ use Rv\Data\Background; use Rv\Data\CollectionElementType; use Rv\Data\Color; use Rv\Data\Cue; +use Rv\Data\Cue\CompletionActionType; +use Rv\Data\Cue\CompletionTargetType; +use Rv\Data\Effect; use Rv\Data\FileProperties; use Rv\Data\Graphics\EdgeInsets; use Rv\Data\Graphics\Element as GraphicsElement; @@ -53,6 +56,7 @@ use Rv\Data\PresentationSlide; use Rv\Data\Slide; use Rv\Data\Slide\Element as SlideElement; use Rv\Data\Slide\Element\TextScroller; +use Rv\Data\Transition; use Rv\Data\URL; use Rv\Data\URL\LocalRelativePath; use Rv\Data\URL\Platform as UrlPlatform; @@ -66,6 +70,7 @@ final class ProFileGenerator array $groups, array $arrangements, array $ccli = [], + array $options = [], ): Song { $presentation = new Presentation(); $presentation->setApplicationInfo(self::buildApplicationInfo()); @@ -141,6 +146,10 @@ final class ProFileGenerator self::applyCcliMetadata($presentation, $ccli); + if (($options['transition'] ?? null) === 'dissolve') { + $presentation->setTransition(self::buildDissolveTransition((float) ($options['transitionDuration'] ?? 1.0))); + } + return new Song($presentation); } @@ -150,8 +159,9 @@ final class ProFileGenerator array $groups, array $arrangements, array $ccli = [], + array $options = [], ): Song { - $song = self::generate($name, $groups, $arrangements, $ccli); + $song = self::generate($name, $groups, $arrangements, $ccli, $options); ProFileWriter::write($song, $filePath); return $song; @@ -254,6 +264,16 @@ final class ProFileGenerator $cue->setName((string) $slideData['label']); } + if (isset($slideData['completion']) && is_array($slideData['completion'])) { + $c = $slideData['completion']; + $cue->setCompletionActionType(CompletionActionType::COMPLETION_ACTION_TYPE_AFTER_TIME); + $cue->setCompletionTime((float) ($c['time'] ?? 0.0)); + $target = (($c['target'] ?? 'next') === 'first') + ? CompletionTargetType::COMPLETION_TARGET_TYPE_FIRST + : CompletionTargetType::COMPLETION_TARGET_TYPE_NEXT; + $cue->setCompletionTargetType($target); + } + return $cue; } @@ -607,6 +627,22 @@ final class ProFileGenerator return $timeline; } + private static function buildDissolveTransition(float $duration = 1.0): Transition + { + $effect = new Effect(); + $effect->setUuid(self::newUuid()); + $effect->setEnabled(true); + $effect->setName('Dissolve'); + $effect->setRenderId('EC52A828-AD85-4602-B70C-1DEE7C904DB6'); + $effect->setCategory('Dissolves'); + + $transition = new Transition(); + $transition->setDuration($duration); + $transition->setEffect($effect); + + return $transition; + } + private static function applyCcliMetadata(Presentation $presentation, array $ccli): void { $metadata = new CCLI(); diff --git a/tests/ProFileGeneratorCompletionTest.php b/tests/ProFileGeneratorCompletionTest.php new file mode 100644 index 0000000..f26a319 --- /dev/null +++ b/tests/ProFileGeneratorCompletionTest.php @@ -0,0 +1,131 @@ + 'V1', + 'color' => [0, 0, 0, 1], + 'slides' => [$slideData], + ], + ]; + } + + private function baseArrangements(): array + { + return [ + ['name' => 'normal', 'groupNames' => ['V1']], + ]; + } + + #[Test] + public function test_cue_completion_advances_to_next_after_time(): void + { + $song = ProFileGenerator::generate( + 'Auto Advance', + $this->baseGroups([ + 'text' => 'Loop me', + 'completion' => ['time' => 10.0, 'target' => 'next'], + ]), + $this->baseArrangements(), + ); + + $cue = $song->getPresentation()->getCues()[0]; + + // COMPLETION_ACTION_TYPE_AFTER_TIME = 3 + $this->assertSame(3, $cue->getCompletionActionType()); + $this->assertSame(10.0, $cue->getCompletionTime()); + // COMPLETION_TARGET_TYPE_NEXT = 1 + $this->assertSame(1, $cue->getCompletionTargetType()); + } + + #[Test] + public function test_cue_completion_loops_to_first(): void + { + $song = ProFileGenerator::generate( + 'Loop To First', + $this->baseGroups([ + 'text' => 'Last slide', + 'completion' => ['time' => 10.0, 'target' => 'first'], + ]), + $this->baseArrangements(), + ); + + $cue = $song->getPresentation()->getCues()[0]; + + $this->assertSame(3, $cue->getCompletionActionType()); + $this->assertSame(10.0, $cue->getCompletionTime()); + // COMPLETION_TARGET_TYPE_FIRST = 4 + $this->assertSame(4, $cue->getCompletionTargetType()); + } + + #[Test] + public function test_dissolve_transition_applied_to_presentation(): void + { + $song = ProFileGenerator::generate( + 'Dissolve Song', + $this->baseGroups(['text' => 'Fade']), + $this->baseArrangements(), + [], + ['transition' => 'dissolve'], + ); + + $presentation = $song->getPresentation(); + $this->assertTrue($presentation->hasTransition()); + + $transition = $presentation->getTransition(); + $this->assertSame(1.0, $transition->getDuration()); + + $effect = $transition->getEffect(); + $this->assertNotNull($effect); + $this->assertTrue($effect->getEnabled()); + $this->assertSame('Dissolve', $effect->getName()); + $this->assertSame('EC52A828-AD85-4602-B70C-1DEE7C904DB6', $effect->getRenderId()); + $this->assertSame('Dissolves', $effect->getCategory()); + } + + #[Test] + public function test_dissolve_transition_honors_custom_duration(): void + { + $song = ProFileGenerator::generate( + 'Dissolve Slow', + $this->baseGroups(['text' => 'Fade']), + $this->baseArrangements(), + [], + ['transition' => 'dissolve', 'transitionDuration' => 2.5], + ); + + $this->assertSame(2.5, $song->getPresentation()->getTransition()->getDuration()); + } + + #[Test] + public function test_backward_compat_no_options_leaves_defaults(): void + { + $song = ProFileGenerator::generate( + 'Plain Song', + $this->baseGroups(['text' => 'Nothing special']), + $this->baseArrangements(), + ); + + $presentation = $song->getPresentation(); + + // No transition configured + $this->assertFalse($presentation->hasTransition()); + + // Cue completion left at default (0 / 0 / 0.0) + $cue = $presentation->getCues()[0]; + $this->assertSame(0, $cue->getCompletionActionType()); + $this->assertSame(0, $cue->getCompletionTargetType()); + $this->assertSame(0.0, $cue->getCompletionTime()); + } +}