[AI] add ProFileGenerator to create .pro files from scratch
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
83fb1a71f4
commit
22d98e2225
421
php/src/ProFileGenerator.php
Normal file
421
php/src/ProFileGenerator.php
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\Action;
|
||||
use Rv\Data\Action\ActionType;
|
||||
use Rv\Data\Action\SlideType;
|
||||
use Rv\Data\ApplicationInfo;
|
||||
use Rv\Data\ApplicationInfo\Application;
|
||||
use Rv\Data\ApplicationInfo\Platform;
|
||||
use Rv\Data\Color;
|
||||
use Rv\Data\Cue;
|
||||
use Rv\Data\Graphics\Element as GraphicsElement;
|
||||
use Rv\Data\Graphics\Feather;
|
||||
use Rv\Data\Graphics\Feather\Style as FeatherStyle;
|
||||
use Rv\Data\Graphics\Fill;
|
||||
use Rv\Data\Graphics\Path;
|
||||
use Rv\Data\Graphics\Path\BezierPoint;
|
||||
use Rv\Data\Graphics\Path\Shape;
|
||||
use Rv\Data\Graphics\Path\Shape\Type as ShapeType;
|
||||
use Rv\Data\Graphics\Point;
|
||||
use Rv\Data\Graphics\Rect;
|
||||
use Rv\Data\Graphics\Shadow;
|
||||
use Rv\Data\Graphics\Shadow\Style as ShadowStyle;
|
||||
use Rv\Data\Graphics\Size;
|
||||
use Rv\Data\Graphics\Stroke;
|
||||
use Rv\Data\Graphics\Stroke\Style as StrokeStyle;
|
||||
use Rv\Data\Graphics\Text;
|
||||
use Rv\Data\Graphics\Text\VerticalAlignment;
|
||||
use Rv\Data\Group;
|
||||
use Rv\Data\Presentation;
|
||||
use Rv\Data\Presentation\Arrangement;
|
||||
use Rv\Data\Presentation\CCLI;
|
||||
use Rv\Data\Presentation\CueGroup;
|
||||
use Rv\Data\PresentationSlide;
|
||||
use Rv\Data\Slide;
|
||||
use Rv\Data\Slide\Element as SlideElement;
|
||||
use Rv\Data\Slide\Element\TextScroller;
|
||||
use Rv\Data\UUID;
|
||||
use Rv\Data\Version;
|
||||
|
||||
final class ProFileGenerator
|
||||
{
|
||||
public static function generate(
|
||||
string $name,
|
||||
array $groups,
|
||||
array $arrangements,
|
||||
array $ccli = [],
|
||||
): Song {
|
||||
$presentation = new Presentation();
|
||||
$presentation->setApplicationInfo(self::buildApplicationInfo());
|
||||
$presentation->setUuid(self::newUuid());
|
||||
$presentation->setName($name);
|
||||
|
||||
$cueGroups = [];
|
||||
$cues = [];
|
||||
$groupUuidsByName = [];
|
||||
|
||||
foreach ($groups as $groupData) {
|
||||
$groupUuid = self::newUuidString();
|
||||
$groupUuidsByName[$groupData['name']] = $groupUuid;
|
||||
|
||||
$group = new Group();
|
||||
$group->setUuid(self::uuidFromString($groupUuid));
|
||||
$group->setName($groupData['name']);
|
||||
$group->setColor(self::colorFromArray($groupData['color']));
|
||||
|
||||
$cueIdentifiers = [];
|
||||
foreach ($groupData['slides'] as $slideData) {
|
||||
$cueUuid = self::newUuidString();
|
||||
$cueIdentifiers[] = self::uuidFromString($cueUuid);
|
||||
$cues[] = self::buildCue($cueUuid, $slideData['text'], $slideData['translation'] ?? null);
|
||||
}
|
||||
|
||||
$cueGroup = new CueGroup();
|
||||
$cueGroup->setGroup($group);
|
||||
$cueGroup->setCueIdentifiers($cueIdentifiers);
|
||||
$cueGroups[] = $cueGroup;
|
||||
}
|
||||
|
||||
$presentation->setCues($cues);
|
||||
$presentation->setCueGroups($cueGroups);
|
||||
|
||||
$arrangementProtos = [];
|
||||
foreach ($arrangements as $arrangementData) {
|
||||
$arrangement = new Arrangement();
|
||||
$arrangement->setUuid(self::newUuid());
|
||||
$arrangement->setName($arrangementData['name']);
|
||||
|
||||
$groupIdentifiers = [];
|
||||
foreach ($arrangementData['groupNames'] as $groupName) {
|
||||
if (!isset($groupUuidsByName[$groupName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupIdentifiers[] = self::uuidFromString($groupUuidsByName[$groupName]);
|
||||
}
|
||||
|
||||
$arrangement->setGroupIdentifiers($groupIdentifiers);
|
||||
$arrangementProtos[] = $arrangement;
|
||||
}
|
||||
|
||||
$presentation->setArrangements($arrangementProtos);
|
||||
if (isset($arrangementProtos[0])) {
|
||||
$presentation->setSelectedArrangement($arrangementProtos[0]->getUuid());
|
||||
}
|
||||
|
||||
self::applyCcliMetadata($presentation, $ccli);
|
||||
|
||||
return new Song($presentation);
|
||||
}
|
||||
|
||||
public static function generateAndWrite(
|
||||
string $filePath,
|
||||
string $name,
|
||||
array $groups,
|
||||
array $arrangements,
|
||||
array $ccli = [],
|
||||
): Song {
|
||||
$song = self::generate($name, $groups, $arrangements, $ccli);
|
||||
ProFileWriter::write($song, $filePath);
|
||||
|
||||
return $song;
|
||||
}
|
||||
|
||||
private static function buildApplicationInfo(): ApplicationInfo
|
||||
{
|
||||
$version = new Version();
|
||||
$version->setBuild('335544354');
|
||||
|
||||
$applicationInfo = new ApplicationInfo();
|
||||
$applicationInfo->setPlatform(Platform::PLATFORM_MACOS);
|
||||
$applicationInfo->setApplication(Application::APPLICATION_PROPRESENTER);
|
||||
$applicationInfo->setPlatformVersion($version);
|
||||
$applicationInfo->setApplicationVersion($version);
|
||||
|
||||
return $applicationInfo;
|
||||
}
|
||||
|
||||
private static function buildCue(string $cueUuid, string $text, ?string $translation): Cue
|
||||
{
|
||||
$elements = [self::buildSlideElement('Orginal', $text)];
|
||||
if ($translation !== null) {
|
||||
$elements[] = self::buildSlideElement('Deutsch', $translation);
|
||||
}
|
||||
|
||||
$slide = new Slide();
|
||||
$slide->setUuid(self::newUuid());
|
||||
$slide->setElements($elements);
|
||||
|
||||
$presentationSlide = new PresentationSlide();
|
||||
$presentationSlide->setBaseSlide($slide);
|
||||
|
||||
$slideType = new SlideType();
|
||||
$slideType->setPresentation($presentationSlide);
|
||||
|
||||
$action = new Action();
|
||||
$action->setUuid(self::newUuid());
|
||||
$action->setType(ActionType::ACTION_TYPE_PRESENTATION_SLIDE);
|
||||
$action->setSlide($slideType);
|
||||
$action->setIsEnabled(true);
|
||||
|
||||
$cue = new Cue();
|
||||
$cue->setUuid(self::uuidFromString($cueUuid));
|
||||
$cue->setActions([$action]);
|
||||
$cue->setIsEnabled(true);
|
||||
|
||||
return $cue;
|
||||
}
|
||||
|
||||
private static function buildSlideElement(string $name, string $text): SlideElement
|
||||
{
|
||||
$graphicsElement = new GraphicsElement();
|
||||
$graphicsElement->setUuid(self::newUuid());
|
||||
$graphicsElement->setName($name);
|
||||
$graphicsElement->setBounds(self::buildBounds());
|
||||
$graphicsElement->setOpacity(1.0);
|
||||
$graphicsElement->setPath(self::buildPath());
|
||||
$graphicsElement->setFill(self::buildFill());
|
||||
$graphicsElement->setStroke(self::buildStroke());
|
||||
$graphicsElement->setShadow(self::buildShadow());
|
||||
$graphicsElement->setFeather(self::buildFeather());
|
||||
|
||||
$graphicsText = new Text();
|
||||
$graphicsText->setRtfData(self::buildRtf($text));
|
||||
$graphicsText->setVerticalAlignment(VerticalAlignment::VERTICAL_ALIGNMENT_MIDDLE);
|
||||
$graphicsElement->setText($graphicsText);
|
||||
|
||||
$slideElement = new SlideElement();
|
||||
$slideElement->setElement($graphicsElement);
|
||||
$slideElement->setInfo(3);
|
||||
$slideElement->setTextScroller(self::buildTextScroller());
|
||||
|
||||
return $slideElement;
|
||||
}
|
||||
|
||||
private static function buildBounds(): Rect
|
||||
{
|
||||
$origin = new Point();
|
||||
$origin->setX(150);
|
||||
$origin->setY(100);
|
||||
|
||||
$size = new Size();
|
||||
$size->setWidth(1620);
|
||||
$size->setHeight(880);
|
||||
|
||||
$rect = new Rect();
|
||||
$rect->setOrigin($origin);
|
||||
$rect->setSize($size);
|
||||
|
||||
return $rect;
|
||||
}
|
||||
|
||||
private static function buildPath(): Path
|
||||
{
|
||||
$path = new Path();
|
||||
$path->setClosed(true);
|
||||
$path->setPoints([
|
||||
self::buildBezierPoint(0.0, 0.0),
|
||||
self::buildBezierPoint(1.0, 0.0),
|
||||
self::buildBezierPoint(1.0, 1.0),
|
||||
self::buildBezierPoint(0.0, 1.0),
|
||||
]);
|
||||
|
||||
$shape = new Shape();
|
||||
$shape->setType(ShapeType::TYPE_RECTANGLE);
|
||||
$path->setShape($shape);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private static function buildBezierPoint(float $x, float $y): BezierPoint
|
||||
{
|
||||
$point = new Point();
|
||||
$point->setX($x);
|
||||
$point->setY($y);
|
||||
|
||||
$bezierPoint = new BezierPoint();
|
||||
$bezierPoint->setPoint($point);
|
||||
$bezierPoint->setQ0($point);
|
||||
$bezierPoint->setQ1($point);
|
||||
$bezierPoint->setCurved(false);
|
||||
|
||||
return $bezierPoint;
|
||||
}
|
||||
|
||||
private static function buildFill(): Fill
|
||||
{
|
||||
$fill = new Fill();
|
||||
$fill->setEnable(true);
|
||||
$fill->setColor(self::buildColor(0.13, 0.59, 0.95, 1.0));
|
||||
|
||||
return $fill;
|
||||
}
|
||||
|
||||
private static function buildStroke(): Stroke
|
||||
{
|
||||
$stroke = new Stroke();
|
||||
$stroke->setStyle(StrokeStyle::STYLE_SOLID_LINE);
|
||||
$stroke->setEnable(true);
|
||||
$stroke->setWidth(3.0);
|
||||
$stroke->setColor(self::buildColor(1.0, 1.0, 1.0, 1.0));
|
||||
|
||||
return $stroke;
|
||||
}
|
||||
|
||||
private static function buildShadow(): Shadow
|
||||
{
|
||||
$shadow = new Shadow();
|
||||
$shadow->setStyle(ShadowStyle::STYLE_DROP);
|
||||
$shadow->setEnable(true);
|
||||
$shadow->setAngle(315.0);
|
||||
$shadow->setOffset(5.0);
|
||||
$shadow->setRadius(5.0);
|
||||
$shadow->setColor(self::buildColor(0.0, 0.0, 0.0, 1.0));
|
||||
$shadow->setOpacity(0.75);
|
||||
|
||||
return $shadow;
|
||||
}
|
||||
|
||||
private static function buildFeather(): Feather
|
||||
{
|
||||
$feather = new Feather();
|
||||
$feather->setStyle(FeatherStyle::STYLE_INSIDE);
|
||||
$feather->setEnable(true);
|
||||
$feather->setRadius(0.05);
|
||||
|
||||
return $feather;
|
||||
}
|
||||
|
||||
private static function buildTextScroller(): TextScroller
|
||||
{
|
||||
$textScroller = new TextScroller();
|
||||
$textScroller->setShouldScroll(true);
|
||||
$textScroller->setScrollRate(0.5);
|
||||
$textScroller->setShouldRepeat(true);
|
||||
$textScroller->setRepeatDistance(0.0617);
|
||||
|
||||
return $textScroller;
|
||||
}
|
||||
|
||||
private static function colorFromArray(array $rgba): Color
|
||||
{
|
||||
return self::buildColor(
|
||||
(float) ($rgba[0] ?? 0.0),
|
||||
(float) ($rgba[1] ?? 0.0),
|
||||
(float) ($rgba[2] ?? 0.0),
|
||||
(float) ($rgba[3] ?? 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
private static function buildColor(float $r, float $g, float $b, float $a): Color
|
||||
{
|
||||
$color = new Color();
|
||||
$color->setRed($r);
|
||||
$color->setGreen($g);
|
||||
$color->setBlue($b);
|
||||
$color->setAlpha($a);
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
private static function applyCcliMetadata(Presentation $presentation, array $ccli): void
|
||||
{
|
||||
if ($ccli === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metadata = new CCLI();
|
||||
if (isset($ccli['author'])) {
|
||||
$metadata->setAuthor((string) $ccli['author']);
|
||||
}
|
||||
if (isset($ccli['song_title'])) {
|
||||
$metadata->setSongTitle((string) $ccli['song_title']);
|
||||
}
|
||||
if (isset($ccli['publisher'])) {
|
||||
$metadata->setPublisher((string) $ccli['publisher']);
|
||||
}
|
||||
if (isset($ccli['copyright_year'])) {
|
||||
$metadata->setCopyrightYear((int) $ccli['copyright_year']);
|
||||
}
|
||||
if (isset($ccli['song_number'])) {
|
||||
$metadata->setSongNumber((int) $ccli['song_number']);
|
||||
}
|
||||
if (isset($ccli['display'])) {
|
||||
$metadata->setDisplay((bool) $ccli['display']);
|
||||
}
|
||||
if (isset($ccli['artist_credits'])) {
|
||||
$metadata->setArtistCredits((string) $ccli['artist_credits']);
|
||||
}
|
||||
if (isset($ccli['album'])) {
|
||||
$metadata->setAlbum((string) $ccli['album']);
|
||||
}
|
||||
|
||||
$presentation->setCcli($metadata);
|
||||
}
|
||||
|
||||
private static function buildRtf(string $text): string
|
||||
{
|
||||
$encodedText = self::encodePlainTextForRtf($text);
|
||||
|
||||
return str_replace('ENCODED_TEXT_HERE', $encodedText, <<<'RTF'
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
|
||||
{\colortbl;\red255\green255\blue255;\red255\green255\blue255;}
|
||||
{\*\expandedcolortbl;;\csgray\c100000;}
|
||||
\deftab1680
|
||||
\pard\pardeftab1680\pardirnatural\qc\partightenfactor0
|
||||
|
||||
\f0\fs84 \cf2 \CocoaLigature0 ENCODED_TEXT_HERE}
|
||||
RTF);
|
||||
}
|
||||
|
||||
private static function encodePlainTextForRtf(string $text): string
|
||||
{
|
||||
$text = str_replace(["\r\n", "\r"], "\n", $text);
|
||||
$text = strtr($text, [
|
||||
'ü' => "\\'fc",
|
||||
'ö' => "\\'f6",
|
||||
'ä' => "\\'e4",
|
||||
'ß' => "\\'df",
|
||||
'Ü' => "\\'dc",
|
||||
'Ö' => "\\'d6",
|
||||
'Ä' => "\\'c4",
|
||||
]);
|
||||
|
||||
return str_replace("\n", "\\\n", $text);
|
||||
}
|
||||
|
||||
private static function newUuid(): UUID
|
||||
{
|
||||
return self::uuidFromString(self::newUuidString());
|
||||
}
|
||||
|
||||
private static function uuidFromString(string $uuid): UUID
|
||||
{
|
||||
$message = new UUID();
|
||||
$message->setString($uuid);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
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);
|
||||
$hex = bin2hex($bytes);
|
||||
|
||||
return sprintf(
|
||||
'%s-%s-%s-%s-%s',
|
||||
substr($hex, 0, 8),
|
||||
substr($hex, 8, 4),
|
||||
substr($hex, 12, 4),
|
||||
substr($hex, 16, 4),
|
||||
substr($hex, 20, 12),
|
||||
);
|
||||
}
|
||||
}
|
||||
321
php/tests/ProFileGeneratorTest.php
Normal file
321
php/tests/ProFileGeneratorTest.php
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser\Tests;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ProPresenter\Parser\ProFileGenerator;
|
||||
use ProPresenter\Parser\ProFileReader;
|
||||
use ProPresenter\Parser\ProFileWriter;
|
||||
|
||||
class ProFileGeneratorTest extends TestCase
|
||||
{
|
||||
private string $tmpDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->tmpDir = sys_get_temp_dir() . '/propresenter-generator-test-' . uniqid();
|
||||
mkdir($this->tmpDir, 0777, true);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (!is_dir($this->tmpDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (scandir($this->tmpDir) ?: [] as $entry) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@unlink($this->tmpDir . '/' . $entry);
|
||||
}
|
||||
|
||||
@rmdir($this->tmpDir);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testGenerateCreatesValidSong(): void
|
||||
{
|
||||
$song = ProFileGenerator::generate(
|
||||
'Simple Song',
|
||||
[
|
||||
[
|
||||
'name' => 'Verse 1',
|
||||
'color' => [0.1, 0.2, 0.3, 1.0],
|
||||
'slides' => [
|
||||
['text' => 'Hello World'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
['name' => 'normal', 'groupNames' => ['Verse 1']],
|
||||
],
|
||||
);
|
||||
|
||||
$this->assertSame('Simple Song', $song->getName());
|
||||
$this->assertCount(1, $song->getGroups());
|
||||
$this->assertCount(1, $song->getSlides());
|
||||
$this->assertSame('Hello World', $song->getSlides()[0]->getPlainText());
|
||||
|
||||
$arrangement = $song->getArrangementByName('normal');
|
||||
$this->assertNotNull($arrangement);
|
||||
$groups = $song->getGroupsForArrangement($arrangement);
|
||||
$this->assertCount(1, $groups);
|
||||
$this->assertSame('Verse 1', $groups[0]->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testGenerateWithMultipleGroupsAndArrangements(): void
|
||||
{
|
||||
$song = ProFileGenerator::generate(
|
||||
'Multi Song',
|
||||
[
|
||||
[
|
||||
'name' => 'Verse 1',
|
||||
'color' => [0.1, 0.2, 0.3, 1.0],
|
||||
'slides' => [
|
||||
['text' => 'V1.1'],
|
||||
['text' => 'V1.2'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Chorus',
|
||||
'color' => [0.4, 0.5, 0.6, 1.0],
|
||||
'slides' => [
|
||||
['text' => 'C1'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
['name' => 'normal', 'groupNames' => ['Verse 1', 'Chorus']],
|
||||
['name' => 'short', 'groupNames' => ['Chorus']],
|
||||
],
|
||||
);
|
||||
|
||||
$this->assertSame(['Verse 1', 'Chorus'], array_map(fn ($group) => $group->getName(), $song->getGroups()));
|
||||
|
||||
$verse1 = $song->getGroupByName('Verse 1');
|
||||
$this->assertNotNull($verse1);
|
||||
$verseSlides = $song->getSlidesForGroup($verse1);
|
||||
$this->assertSame(['V1.1', 'V1.2'], array_map(fn ($slide) => $slide->getPlainText(), $verseSlides));
|
||||
|
||||
$chorus = $song->getGroupByName('Chorus');
|
||||
$this->assertNotNull($chorus);
|
||||
$chorusSlides = $song->getSlidesForGroup($chorus);
|
||||
$this->assertSame(['C1'], array_map(fn ($slide) => $slide->getPlainText(), $chorusSlides));
|
||||
|
||||
$normal = $song->getArrangementByName('normal');
|
||||
$this->assertNotNull($normal);
|
||||
$this->assertSame(
|
||||
['Verse 1', 'Chorus'],
|
||||
array_map(fn ($group) => $group->getName(), $song->getGroupsForArrangement($normal)),
|
||||
);
|
||||
|
||||
$short = $song->getArrangementByName('short');
|
||||
$this->assertNotNull($short);
|
||||
$this->assertSame(
|
||||
['Chorus'],
|
||||
array_map(fn ($group) => $group->getName(), $song->getGroupsForArrangement($short)),
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testGenerateWithTranslation(): void
|
||||
{
|
||||
$song = ProFileGenerator::generate(
|
||||
'Translation Song',
|
||||
[
|
||||
[
|
||||
'name' => 'Verse 1',
|
||||
'color' => [0.1, 0.2, 0.3, 1.0],
|
||||
'slides' => [
|
||||
['text' => 'Original', 'translation' => 'Translated'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
['name' => 'normal', 'groupNames' => ['Verse 1']],
|
||||
],
|
||||
);
|
||||
|
||||
$slide = $song->getSlides()[0];
|
||||
$this->assertTrue($slide->hasTranslation());
|
||||
$this->assertSame('Translated', $slide->getTranslation()?->getPlainText());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testGenerateWithCcliMetadata(): void
|
||||
{
|
||||
$song = ProFileGenerator::generate(
|
||||
'CCLI Song',
|
||||
[
|
||||
[
|
||||
'name' => 'Verse 1',
|
||||
'color' => [0.1, 0.2, 0.3, 1.0],
|
||||
'slides' => [
|
||||
['text' => 'Line'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
['name' => 'normal', 'groupNames' => ['Verse 1']],
|
||||
],
|
||||
[
|
||||
'author' => 'Author Name',
|
||||
'song_title' => 'Song Title',
|
||||
'publisher' => 'Publisher Name',
|
||||
'copyright_year' => 2024,
|
||||
'song_number' => 12345,
|
||||
'display' => true,
|
||||
'artist_credits' => 'Artist Credits',
|
||||
'album' => 'Album Name',
|
||||
],
|
||||
);
|
||||
|
||||
$this->assertSame('Author Name', $song->getCcliAuthor());
|
||||
$this->assertSame('Song Title', $song->getCcliSongTitle());
|
||||
$this->assertSame('Publisher Name', $song->getCcliPublisher());
|
||||
$this->assertSame(2024, $song->getCcliCopyrightYear());
|
||||
$this->assertSame(12345, $song->getCcliSongNumber());
|
||||
$this->assertTrue($song->getCcliDisplay());
|
||||
$this->assertSame('Artist Credits', $song->getCcliArtistCredits());
|
||||
$this->assertSame('Album Name', $song->getCcliAlbum());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testRoundTripFromTestPro(): void
|
||||
{
|
||||
$original = ProFileReader::read('/Users/thorsten/AI/propresenter/ref/Test.pro');
|
||||
|
||||
$groups = [];
|
||||
foreach ($original->getGroups() as $group) {
|
||||
$color = $group->getColor();
|
||||
$slides = [];
|
||||
foreach ($original->getSlidesForGroup($group) as $slide) {
|
||||
$slides[] = [
|
||||
'text' => $slide->getPlainText(),
|
||||
'translation' => $slide->hasTranslation() ? $slide->getTranslation()?->getPlainText() : null,
|
||||
];
|
||||
}
|
||||
|
||||
$groups[] = [
|
||||
'name' => $group->getName(),
|
||||
'color' => [
|
||||
$color['r'] ?? 0.0,
|
||||
$color['g'] ?? 0.0,
|
||||
$color['b'] ?? 0.0,
|
||||
$color['a'] ?? 1.0,
|
||||
],
|
||||
'slides' => $slides,
|
||||
];
|
||||
}
|
||||
|
||||
$arrangements = [];
|
||||
foreach ($original->getArrangements() as $arrangement) {
|
||||
$arrangements[] = [
|
||||
'name' => $arrangement->getName(),
|
||||
'groupNames' => array_map(
|
||||
fn ($group) => $group->getName(),
|
||||
$original->getGroupsForArrangement($arrangement),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
$ccli = [
|
||||
'author' => $original->getCcliAuthor(),
|
||||
'song_title' => $original->getCcliSongTitle(),
|
||||
'publisher' => $original->getCcliPublisher(),
|
||||
'copyright_year' => $original->getCcliCopyrightYear(),
|
||||
'song_number' => $original->getCcliSongNumber(),
|
||||
'display' => $original->getCcliDisplay(),
|
||||
'artist_credits' => $original->getCcliArtistCredits(),
|
||||
'album' => $original->getCcliAlbum(),
|
||||
];
|
||||
|
||||
$generated = ProFileGenerator::generate($original->getName(), $groups, $arrangements, $ccli);
|
||||
$filePath = $this->tmpDir . '/test-roundtrip.pro';
|
||||
ProFileWriter::write($generated, $filePath);
|
||||
$roundTrip = ProFileReader::read($filePath);
|
||||
|
||||
$this->assertSame($original->getName(), $roundTrip->getName());
|
||||
|
||||
$this->assertSame(
|
||||
array_map(fn ($group) => $group->getName(), $original->getGroups()),
|
||||
array_map(fn ($group) => $group->getName(), $roundTrip->getGroups()),
|
||||
);
|
||||
|
||||
foreach ($original->getGroups() as $group) {
|
||||
$actualGroup = $roundTrip->getGroupByName($group->getName());
|
||||
$this->assertNotNull($actualGroup);
|
||||
|
||||
$expectedSlides = $original->getSlidesForGroup($group);
|
||||
$actualSlides = $roundTrip->getSlidesForGroup($actualGroup);
|
||||
$this->assertCount(count($expectedSlides), $actualSlides);
|
||||
|
||||
foreach ($expectedSlides as $index => $expectedSlide) {
|
||||
$actualSlide = $actualSlides[$index];
|
||||
$this->assertSame($expectedSlide->getPlainText(), $actualSlide->getPlainText());
|
||||
$this->assertSame($expectedSlide->hasTranslation(), $actualSlide->hasTranslation());
|
||||
if ($expectedSlide->hasTranslation()) {
|
||||
$this->assertSame(
|
||||
$expectedSlide->getTranslation()?->getPlainText(),
|
||||
$actualSlide->getTranslation()?->getPlainText(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame(
|
||||
array_map(fn ($arrangement) => $arrangement->getName(), $original->getArrangements()),
|
||||
array_map(fn ($arrangement) => $arrangement->getName(), $roundTrip->getArrangements()),
|
||||
);
|
||||
|
||||
foreach ($original->getArrangements() as $arrangement) {
|
||||
$roundTripArrangement = $roundTrip->getArrangementByName($arrangement->getName());
|
||||
$this->assertNotNull($roundTripArrangement);
|
||||
|
||||
$expectedNames = array_map(
|
||||
fn ($group) => $group->getName(),
|
||||
$original->getGroupsForArrangement($arrangement),
|
||||
);
|
||||
$actualNames = array_map(
|
||||
fn ($group) => $group->getName(),
|
||||
$roundTrip->getGroupsForArrangement($roundTripArrangement),
|
||||
);
|
||||
|
||||
$this->assertSame($expectedNames, $actualNames);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testGenerateAndWriteCreatesFile(): void
|
||||
{
|
||||
$filePath = $this->tmpDir . '/generated.pro';
|
||||
|
||||
ProFileGenerator::generateAndWrite(
|
||||
$filePath,
|
||||
'Write Song',
|
||||
[
|
||||
[
|
||||
'name' => 'Verse 1',
|
||||
'color' => [0.1, 0.2, 0.3, 1.0],
|
||||
'slides' => [
|
||||
['text' => 'Line 1'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
['name' => 'normal', 'groupNames' => ['Verse 1']],
|
||||
],
|
||||
);
|
||||
|
||||
$this->assertFileExists($filePath);
|
||||
|
||||
$song = ProFileReader::read($filePath);
|
||||
$this->assertSame('Write Song', $song->getName());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue