feat(export): role+name nametag titles and combined keyvisual+nametag sermon intro
This commit is contained in:
parent
e849d43b74
commit
e47299f90a
|
|
@ -74,8 +74,10 @@ private function generatePlaylistFromAgenda(Service $service, Collection $agenda
|
||||||
|
|
||||||
foreach ($agendaItems as $item) {
|
foreach ($agendaItems as $item) {
|
||||||
if ($item->id === $firstVisibleItemId && $moderatorSlideData !== null) {
|
if ($item->id === $firstVisibleItemId && $moderatorSlideData !== null) {
|
||||||
|
$moderatorName = trim((string) ($moderatorSlideData['text'] ?? ''));
|
||||||
|
$moderatorPresentationName = $moderatorName !== '' ? 'Moderator - '.$moderatorName : 'Moderator';
|
||||||
$this->writeProAndEmbed(
|
$this->writeProAndEmbed(
|
||||||
'Moderator',
|
$moderatorPresentationName,
|
||||||
$moderatorSlideData,
|
$moderatorSlideData,
|
||||||
$tempDir,
|
$tempDir,
|
||||||
$playlistItems,
|
$playlistItems,
|
||||||
|
|
@ -136,12 +138,30 @@ private function generatePlaylistFromAgenda(Service $service, Collection $agenda
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($item->slides->isNotEmpty()) {
|
$isSermon = $this->backgroundPartTypeForAgendaItem($item) === 'sermon';
|
||||||
if ($this->backgroundPartTypeForAgendaItem($item) === 'sermon') {
|
|
||||||
$this->addKeyVisualSlide($service, $tempDir, $playlistItems, $embeddedFiles, 'Keyvisual-Predigt');
|
|
||||||
$this->addPreacherNameTag($service, $tempDir, $playlistItems, $embeddedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if ($isSermon) {
|
||||||
|
$this->addSermonIntroPresentation($service, $tempDir, $playlistItems, $embeddedFiles);
|
||||||
|
|
||||||
|
if ($item->slides->isNotEmpty()) {
|
||||||
|
$countBefore = count($playlistItems);
|
||||||
|
$label = $item->title ?: 'Folien';
|
||||||
|
$this->addSlidesFromCollection(
|
||||||
|
$item->slides,
|
||||||
|
'agenda_'.$item->id,
|
||||||
|
$label,
|
||||||
|
$tempDir,
|
||||||
|
$playlistItems,
|
||||||
|
$embeddedFiles,
|
||||||
|
$service,
|
||||||
|
$this->backgroundPartTypeForAgendaItem($item),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (count($playlistItems) > $countBefore) {
|
||||||
|
$realContentEmitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($item->slides->isNotEmpty()) {
|
||||||
$countBefore = count($playlistItems);
|
$countBefore = count($playlistItems);
|
||||||
$label = $item->title ?: 'Folien';
|
$label = $item->title ?: 'Folien';
|
||||||
$this->addSlidesFromCollection(
|
$this->addSlidesFromCollection(
|
||||||
|
|
@ -158,11 +178,7 @@ private function generatePlaylistFromAgenda(Service $service, Collection $agenda
|
||||||
if (count($playlistItems) > $countBefore) {
|
if (count($playlistItems) > $countBefore) {
|
||||||
$realContentEmitted = true;
|
$realContentEmitted = true;
|
||||||
}
|
}
|
||||||
|
} elseif (! $this->isNameTagAgendaItem($item)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $this->isNameTagAgendaItem($item)) {
|
|
||||||
if ($realContentEmitted && ! $keyvisualFallbackEmitted) {
|
if ($realContentEmitted && ! $keyvisualFallbackEmitted) {
|
||||||
$this->addKeyVisualFallbackPresentation(
|
$this->addKeyVisualFallbackPresentation(
|
||||||
$item,
|
$item,
|
||||||
|
|
@ -719,27 +735,50 @@ private function buildPreacherSlideData(Service $service): ?array
|
||||||
return app(NameTagSlideBuilder::class)->buildPreacherSlide($name);
|
return app(NameTagSlideBuilder::class)->buildPreacherSlide($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addKeyVisualSlide(Service $service, string $tempDir, array &$playlistItems, array &$embeddedFiles, string $label = 'Keyvisual'): void
|
/**
|
||||||
|
* Emit the sermon intro as ONE presentation with up to two cues:
|
||||||
|
* slide 1 = key-visual (image only)
|
||||||
|
* slide 2 = key-visual WITH the preacher name tag rendered on top
|
||||||
|
*
|
||||||
|
* The presentation is named after the preacher ("Prediger - <Name>") when a
|
||||||
|
* name tag is configured, otherwise "Keyvisual-Predigt". Emits nothing when
|
||||||
|
* neither a key-visual nor a name tag is available.
|
||||||
|
*/
|
||||||
|
private function addSermonIntroPresentation(Service $service, string $tempDir, array &$playlistItems, array &$embeddedFiles): void
|
||||||
{
|
{
|
||||||
$kvData = $this->keyVisualData($service);
|
$kvData = $this->keyVisualData($service);
|
||||||
if ($kvData === null) {
|
$nameTagData = $this->buildPreacherSlideData($service);
|
||||||
|
|
||||||
|
if ($kvData === null && $nameTagData === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->embedKeyVisual($service, $embeddedFiles);
|
$slides = [];
|
||||||
|
|
||||||
$slideData = ['imageOnly' => true, 'background' => $kvData];
|
if ($kvData !== null) {
|
||||||
$this->writeProAndEmbed($label, $slideData, $tempDir, $playlistItems, $embeddedFiles);
|
$this->embedKeyVisual($service, $embeddedFiles);
|
||||||
}
|
$slides[] = ['imageOnly' => true, 'background' => $kvData];
|
||||||
|
|
||||||
private function addPreacherNameTag(Service $service, string $tempDir, array &$playlistItems, array &$embeddedFiles): void
|
|
||||||
{
|
|
||||||
$slideData = $this->buildPreacherSlideData($service);
|
|
||||||
if ($slideData === null) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->writeProAndEmbed('Predigername', $slideData, $tempDir, $playlistItems, $embeddedFiles);
|
if ($nameTagData !== null) {
|
||||||
|
if ($kvData !== null) {
|
||||||
|
$nameTagData['background'] = $kvData;
|
||||||
|
}
|
||||||
|
$slides[] = $nameTagData;
|
||||||
|
|
||||||
|
$preacher = trim((string) ($nameTagData['text'] ?? ''));
|
||||||
|
$name = $preacher !== '' ? 'Prediger - '.$preacher : 'Prediger';
|
||||||
|
} else {
|
||||||
|
$name = 'Keyvisual-Predigt';
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = [['name' => $name, 'color' => [0, 0, 0, 1], 'slides' => $slides]];
|
||||||
|
$arrangements = [['name' => 'normal', 'groupNames' => [$name]]];
|
||||||
|
$filename = preg_replace('/[^a-zA-Z0-9äöüÄÖÜß\-_ ]/', '', $name).'-'.uniqid().'.pro';
|
||||||
|
$path = $tempDir.'/'.$filename;
|
||||||
|
$this->writeProFile($path, $name, $groups, $arrangements);
|
||||||
|
$embeddedFiles[$filename] = file_get_contents($path);
|
||||||
|
$playlistItems[] = ['type' => 'presentation', 'name' => $name, 'path' => $filename];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function writeProAndEmbed(string $name, array $slideData, string $tempDir, array &$playlistItems, array &$embeddedFiles): void
|
private function writeProAndEmbed(string $name, array $slideData, string $tempDir, array &$playlistItems, array &$embeddedFiles): void
|
||||||
|
|
|
||||||
|
|
@ -94,23 +94,20 @@ public function test_full_service_playlist_includes_all_features_in_correct_orde
|
||||||
$playlist = ProPlaylistReader::read($result['path']);
|
$playlist = ProPlaylistReader::read($result['path']);
|
||||||
$names = array_map(fn ($entry) => $entry->getName(), $playlist->getEntries());
|
$names = array_map(fn ($entry) => $entry->getName(), $playlist->getEntries());
|
||||||
|
|
||||||
$moderatorIndex = array_search('Moderator', $names, true);
|
$moderatorIndex = array_search('Moderator - Moderator Max', $names, true);
|
||||||
$songIndex = array_search('Großer Gott', $names, true);
|
$songIndex = array_search('Großer Gott', $names, true);
|
||||||
$kvIndex = array_search('Keyvisual-Predigt', $names, true);
|
$introIndex = array_search('Prediger - Pastor Paul', $names, true);
|
||||||
$preacherIndex = array_search('Predigername', $names, true);
|
|
||||||
$sermonIndex = array_search('Predigt', $names, true);
|
$sermonIndex = array_search('Predigt', $names, true);
|
||||||
|
|
||||||
$this->assertNotFalse($moderatorIndex, 'Moderator nametag missing');
|
$this->assertNotFalse($moderatorIndex, 'Moderator nametag missing');
|
||||||
$this->assertNotFalse($songIndex, 'Song presentation missing');
|
$this->assertNotFalse($songIndex, 'Song presentation missing');
|
||||||
$this->assertNotFalse($kvIndex, 'Keyvisual-Predigt entry missing');
|
$this->assertNotFalse($introIndex, 'Prediger intro (keyvisual + nametag) missing');
|
||||||
$this->assertNotFalse($preacherIndex, 'Predigername (preacher nametag) missing');
|
|
||||||
$this->assertNotFalse($sermonIndex, 'Predigt (sermon slides) missing');
|
$this->assertNotFalse($sermonIndex, 'Predigt (sermon slides) missing');
|
||||||
|
|
||||||
$this->assertSame(0, $moderatorIndex, 'Moderator nametag must be first');
|
$this->assertSame(0, $moderatorIndex, 'Moderator nametag must be first');
|
||||||
$this->assertLessThan($songIndex, $moderatorIndex);
|
$this->assertLessThan($songIndex, $moderatorIndex);
|
||||||
$this->assertLessThan($kvIndex, $songIndex);
|
$this->assertLessThan($introIndex, $songIndex);
|
||||||
$this->assertLessThan($preacherIndex, $kvIndex, 'Keyvisual must come before preacher nametag');
|
$this->assertLessThan($sermonIndex, $introIndex, 'Prediger intro must come before sermon slides');
|
||||||
$this->assertLessThan($sermonIndex, $preacherIndex, 'Preacher nametag must come before sermon slides');
|
|
||||||
|
|
||||||
$songParser = $playlist->getEmbeddedSong('Großer Gott.pro');
|
$songParser = $playlist->getEmbeddedSong('Großer Gott.pro');
|
||||||
$this->assertNotNull($songParser, 'Embedded song .pro missing');
|
$this->assertNotNull($songParser, 'Embedded song .pro missing');
|
||||||
|
|
|
||||||
|
|
@ -55,25 +55,30 @@ public function test_sermon_sequence_is_keyvisual_preacher_nametag_then_uploaded
|
||||||
$entries = $playlist->getEntries();
|
$entries = $playlist->getEntries();
|
||||||
|
|
||||||
$names = $this->entryNames($playlist);
|
$names = $this->entryNames($playlist);
|
||||||
$offset = $names[0] === 'Moderator' ? 1 : 0;
|
// A moderator name tag may precede the sermon intro when the (factory-random)
|
||||||
$this->assertSame(['Keyvisual-Predigt', 'Predigername', 'Predigt'], array_slice($names, $offset));
|
// responsible person resolves to a moderator; skip it if present.
|
||||||
|
$offset = str_starts_with((string) ($names[0] ?? ''), 'Moderator') ? 1 : 0;
|
||||||
|
$this->assertSame(['Prediger - Erika Predigt', 'Predigt'], array_slice($names, $offset));
|
||||||
|
|
||||||
$keyVisualSlides = $this->slidesForEntry($playlist, $entries[$offset]);
|
// The sermon intro is ONE presentation with two cues: the key-visual alone,
|
||||||
$this->assertCount(1, $keyVisualSlides);
|
// then the same key-visual with the preacher name tag rendered on top.
|
||||||
$this->assertTrue($keyVisualSlides[0]->hasBackgroundMedia());
|
$introSlides = $this->slidesForEntry($playlist, $entries[$offset]);
|
||||||
$this->assertSame('KEY_VISUAL.jpg', $keyVisualSlides[0]->getBackgroundMediaUrl());
|
$this->assertCount(2, $introSlides);
|
||||||
|
|
||||||
|
$this->assertTrue($introSlides[0]->hasBackgroundMedia());
|
||||||
|
$this->assertSame('KEY_VISUAL.jpg', $introSlides[0]->getBackgroundMediaUrl());
|
||||||
$this->assertArrayHasKey('KEY_VISUAL.jpg', $playlist->getEmbeddedMediaFiles());
|
$this->assertArrayHasKey('KEY_VISUAL.jpg', $playlist->getEmbeddedMediaFiles());
|
||||||
|
|
||||||
$nameTagSlides = $this->slidesForEntry($playlist, $entries[$offset + 1]);
|
$this->assertTrue($introSlides[1]->hasBackgroundMedia());
|
||||||
$this->assertCount(1, $nameTagSlides);
|
$this->assertSame('KEY_VISUAL.jpg', $introSlides[1]->getBackgroundMediaUrl());
|
||||||
// Name and role are now split: the name is the main (\fs84) run and the
|
// Name and role are split: the name is the main (\fs84) run and the
|
||||||
// role is a separate smaller, non-bold (\b0\fs50) subtitle run.
|
// role is a separate smaller, non-bold (\b0\fs50) subtitle run.
|
||||||
[$name, $subtitle] = $this->nameTagNameAndSubtitle($nameTagSlides[0]);
|
[$name, $subtitle] = $this->nameTagNameAndSubtitle($introSlides[1]);
|
||||||
$this->assertSame('Erika Predigt', $name);
|
$this->assertSame('Erika Predigt', $name);
|
||||||
$this->assertSame('Predigt', $subtitle);
|
$this->assertSame('Predigt', $subtitle);
|
||||||
$this->assertTrue($nameTagSlides[0]->hasMacro());
|
$this->assertTrue($introSlides[1]->hasMacro());
|
||||||
|
|
||||||
$sermonSlides = $this->slidesForEntry($playlist, $entries[$offset + 2]);
|
$sermonSlides = $this->slidesForEntry($playlist, $entries[$offset + 1]);
|
||||||
$this->assertCount(2, $sermonSlides);
|
$this->assertCount(2, $sermonSlides);
|
||||||
$this->assertSame('sermon-1.jpg', $sermonSlides[0]->getLabel());
|
$this->assertSame('sermon-1.jpg', $sermonSlides[0]->getLabel());
|
||||||
$this->assertSame('sermon-2.jpg', $sermonSlides[1]->getLabel());
|
$this->assertSame('sermon-2.jpg', $sermonSlides[1]->getLabel());
|
||||||
|
|
@ -117,7 +122,7 @@ public function test_moderator_nametag_is_first_presentation_for_first_visible_a
|
||||||
$playlist = ProPlaylistReader::read($result['path']);
|
$playlist = ProPlaylistReader::read($result['path']);
|
||||||
$entries = $playlist->getEntries();
|
$entries = $playlist->getEntries();
|
||||||
|
|
||||||
$this->assertSame(['Moderator', 'Erstes sichtbares Lied'], $this->entryNames($playlist));
|
$this->assertSame(['Moderator - Max Moderation', 'Erstes sichtbares Lied'], $this->entryNames($playlist));
|
||||||
$moderatorSlides = $this->slidesForEntry($playlist, $entries[0]);
|
$moderatorSlides = $this->slidesForEntry($playlist, $entries[0]);
|
||||||
$this->assertCount(1, $moderatorSlides);
|
$this->assertCount(1, $moderatorSlides);
|
||||||
// Name and role are split: the name is the main run, the role the subtitle run.
|
// Name and role are split: the name is the main run, the role the subtitle run.
|
||||||
|
|
@ -203,6 +208,99 @@ public function test_without_moderator_name_no_moderator_nametag_is_added(): voi
|
||||||
$this->cleanupTempDir($result['temp_dir']);
|
$this->cleanupTempDir($result['temp_dir']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_nametag_presentations_are_named_by_role_and_person(): void
|
||||||
|
{
|
||||||
|
$this->configureNameTagMacro();
|
||||||
|
Storage::disk('public')->put('slides/keyvisual.jpg', 'keyvisual-image');
|
||||||
|
Storage::disk('public')->put('slides/sermon.jpg', 'sermon-image');
|
||||||
|
|
||||||
|
$service = Service::factory()->create([
|
||||||
|
'title' => 'Rollen Benennung',
|
||||||
|
'date' => now(),
|
||||||
|
'key_visual_filename' => 'slides/keyvisual.jpg',
|
||||||
|
'moderator_name' => 'Max Moderation',
|
||||||
|
'preacher_name_override' => 'Erika Predigt',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$song = $this->createSongWithContent('Eröffnungslied');
|
||||||
|
$serviceSong = ServiceSong::create([
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'song_id' => $song->id,
|
||||||
|
'cts_song_name' => 'Eröffnungslied',
|
||||||
|
'order' => 1,
|
||||||
|
]);
|
||||||
|
ServiceAgendaItem::factory()->create([
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'title' => 'Eröffnungslied',
|
||||||
|
'service_song_id' => $serviceSong->id,
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_before_event' => false,
|
||||||
|
]);
|
||||||
|
$sermonItem = ServiceAgendaItem::factory()->create([
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'title' => 'Predigt',
|
||||||
|
'service_song_id' => null,
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_before_event' => false,
|
||||||
|
]);
|
||||||
|
$this->createSermonSlide($service, $sermonItem, 'sermon.jpg', 0);
|
||||||
|
|
||||||
|
$result = app(PlaylistExportService::class)->generatePlaylist($service);
|
||||||
|
$playlist = ProPlaylistReader::read($result['path']);
|
||||||
|
$names = $this->entryNames($playlist);
|
||||||
|
|
||||||
|
$this->assertContains('Moderator - Max Moderation', $names);
|
||||||
|
$this->assertContains('Prediger - Erika Predigt', $names);
|
||||||
|
|
||||||
|
$this->cleanupTempDir($result['temp_dir']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_sermon_item_without_uploaded_slides_still_emits_intro_and_no_images_presentation(): void
|
||||||
|
{
|
||||||
|
$this->configureNameTagMacro();
|
||||||
|
Setting::set('agenda_sermon_matching', 'Predigt');
|
||||||
|
Storage::disk('public')->put('slides/keyvisual.jpg', 'keyvisual-image');
|
||||||
|
|
||||||
|
$service = Service::factory()->create([
|
||||||
|
'title' => 'Predigt ohne Folien',
|
||||||
|
'date' => now(),
|
||||||
|
'key_visual_filename' => 'slides/keyvisual.jpg',
|
||||||
|
'preacher_name_override' => 'Erika Predigt',
|
||||||
|
]);
|
||||||
|
ServiceAgendaItem::factory()->create([
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'title' => 'Predigt',
|
||||||
|
'service_song_id' => null,
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_before_event' => false,
|
||||||
|
'responsible' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = app(PlaylistExportService::class)->generatePlaylist($service);
|
||||||
|
$playlist = ProPlaylistReader::read($result['path']);
|
||||||
|
$entries = $playlist->getEntries();
|
||||||
|
|
||||||
|
// A sermon item with no uploaded slides emits ONLY the intro presentation.
|
||||||
|
$this->assertSame(['Prediger - Erika Predigt'], $this->entryNames($playlist));
|
||||||
|
|
||||||
|
// The intro .pro carries both cues: key-visual alone + key-visual with name tag.
|
||||||
|
$introSlides = $this->slidesForEntry($playlist, $entries[0]);
|
||||||
|
$this->assertCount(2, $introSlides);
|
||||||
|
$this->assertTrue($introSlides[0]->hasBackgroundMedia());
|
||||||
|
$this->assertSame('KEY_VISUAL.jpg', $introSlides[0]->getBackgroundMediaUrl());
|
||||||
|
$this->assertTrue($introSlides[1]->hasBackgroundMedia());
|
||||||
|
$this->assertSame('KEY_VISUAL.jpg', $introSlides[1]->getBackgroundMediaUrl());
|
||||||
|
[$name, $subtitle] = $this->nameTagNameAndSubtitle($introSlides[1]);
|
||||||
|
$this->assertSame('Erika Predigt', $name);
|
||||||
|
$this->assertSame('Predigt', $subtitle);
|
||||||
|
$this->assertTrue($introSlides[1]->hasMacro());
|
||||||
|
|
||||||
|
// No separate sermon-images presentation is emitted.
|
||||||
|
$this->assertNull($playlist->getEmbeddedSong('Predigt.pro'));
|
||||||
|
|
||||||
|
$this->cleanupTempDir($result['temp_dir']);
|
||||||
|
}
|
||||||
|
|
||||||
private function configureNameTagMacro(): void
|
private function configureNameTagMacro(): void
|
||||||
{
|
{
|
||||||
Setting::set('namenseinblender_macro_name', 'Namenseinblender');
|
Setting::set('namenseinblender_macro_name', 'Namenseinblender');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue