feat(generator): add cue auto-advance/loop completion and dissolve transition
This commit is contained in:
parent
d187b092e3
commit
01654209c6
|
|
@ -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();
|
||||
|
|
|
|||
131
tests/ProFileGeneratorCompletionTest.php
Normal file
131
tests/ProFileGeneratorCompletionTest.php
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser\Tests;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ProPresenter\Parser\ProFileGenerator;
|
||||
|
||||
class ProFileGeneratorCompletionTest extends TestCase
|
||||
{
|
||||
private function baseGroups(array $slideData): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => '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());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue