feat:튜토리얼 영상 멀티스텝 개선 (8초 → 30초~2분)
- ScreenAnalysisService: Gemini 프롬프트를 멀티스텝(3~5 steps) 출력으로 변경 + 하위 호환 fallback - SlideAnnotationService: 스포트라이트 효과(annotateSlideWithSpotlight), 인트로/아웃트로 슬라이드 생성 - TutorialVideoJob: screen→steps 중첩 루프 + 인트로/아웃트로 씬 추가 - index.blade.php: 단계별 나레이션 편집 UI + 예상 시간 표시 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,15 +33,21 @@ public function analyzeScreenshots(array $imagePaths): array
|
||||
$result = $this->analyzeSingleScreen($imagePath, $screenNumber, count($imagePaths));
|
||||
|
||||
if ($result) {
|
||||
$result = $this->ensureStepsFormat($result);
|
||||
$results[] = $result;
|
||||
} else {
|
||||
Log::warning("ScreenAnalysis: 스크린샷 {$screenNumber} 분석 실패, 기본값 사용");
|
||||
$results[] = [
|
||||
'screen_number' => $screenNumber,
|
||||
'title' => "화면 {$screenNumber}",
|
||||
'narration' => "이 화면에서는 주요 기능을 확인할 수 있습니다.",
|
||||
'ui_elements' => [],
|
||||
'duration' => 8,
|
||||
'steps' => [
|
||||
[
|
||||
'step_number' => 1,
|
||||
'narration' => '이 화면에서는 주요 기능을 확인할 수 있습니다.',
|
||||
'focused_element' => null,
|
||||
'duration' => 6,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -49,6 +55,63 @@ public function analyzeScreenshots(array $imagePaths): array
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 형식(narration/ui_elements) → steps[] 형식으로 변환 (하위 호환)
|
||||
* Job에서 DB 캐시된 기존 형식 변환에도 사용
|
||||
*/
|
||||
public function ensureStepsFormatPublic(array $screen): array
|
||||
{
|
||||
return $this->ensureStepsFormat($screen);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 형식(narration/ui_elements) → steps[] 형식으로 변환 (하위 호환)
|
||||
*/
|
||||
private function ensureStepsFormat(array $screen): array
|
||||
{
|
||||
if (! empty($screen['steps'])) {
|
||||
return $screen;
|
||||
}
|
||||
|
||||
$narration = $screen['narration'] ?? '이 화면의 기능을 확인할 수 있습니다.';
|
||||
$uiElements = $screen['ui_elements'] ?? [];
|
||||
$duration = $screen['duration'] ?? 8;
|
||||
|
||||
if (empty($uiElements)) {
|
||||
$screen['steps'] = [
|
||||
[
|
||||
'step_number' => 1,
|
||||
'narration' => $narration,
|
||||
'focused_element' => null,
|
||||
'duration' => $duration,
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$steps = [];
|
||||
$sentences = preg_split('/(?<=[.。])\s*/u', $narration, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ($uiElements as $i => $el) {
|
||||
$steps[] = [
|
||||
'step_number' => $i + 1,
|
||||
'narration' => $sentences[$i] ?? $narration,
|
||||
'focused_element' => [
|
||||
'type' => $el['type'] ?? 'other',
|
||||
'label' => $el['label'] ?? '',
|
||||
'x' => $el['x'] ?? 0.5,
|
||||
'y' => $el['y'] ?? 0.5,
|
||||
'w' => 0.2,
|
||||
'h' => 0.15,
|
||||
],
|
||||
'duration' => max(5, (int) ($duration / count($uiElements))),
|
||||
];
|
||||
}
|
||||
$screen['steps'] = $steps;
|
||||
}
|
||||
|
||||
unset($screen['narration'], $screen['ui_elements'], $screen['duration']);
|
||||
|
||||
return $screen;
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 스크린샷 분석
|
||||
*/
|
||||
@@ -67,40 +130,53 @@ private function analyzeSingleScreen(string $imagePath, int $screenNumber, int $
|
||||
|
||||
이 스크린샷은 SAM 시스템의 화면입니다. (화면 {$screenNumber}/{$totalScreens})
|
||||
|
||||
이 화면을 분석하고 사용자 튜토리얼 나레이션을 작성하세요.
|
||||
이 화면을 분석하고, 사용자가 따라할 수 있는 **3~5개 단계(steps)**로 나누어 튜토리얼을 작성하세요.
|
||||
각 단계마다 화면에서 집중해야 할 UI 영역(focused_element)을 지정합니다.
|
||||
|
||||
=== 분석 요구사항 ===
|
||||
1. 화면의 주요 목적/기능을 파악
|
||||
2. 주요 UI 요소 식별 (버튼, 입력폼, 테이블, 메뉴, 탭 등)
|
||||
3. 각 UI 요소의 대략적인 위치를 화면 비율(0~1)로 표시
|
||||
4. 사용자가 이 화면에서 수행할 작업 순서를 안내하는 나레이션 작성
|
||||
2. 사용자가 수행할 작업을 3~5단계로 분해
|
||||
3. 각 단계마다 집중할 UI 영역의 위치와 크기를 비율(0~1)로 표시
|
||||
4. 단계별 나레이션은 독립적인 문장으로 작성
|
||||
|
||||
=== 나레이션 작성 규칙 ===
|
||||
- 친근한 존댓말 사용 (예: "~하실 수 있습니다", "~을 클릭하세요")
|
||||
- TTS로 읽을 것이므로 이모지/특수기호 금지
|
||||
- 순수 한글 텍스트만 작성
|
||||
- 문장은 마침표로 끝내기
|
||||
- 전체 나레이션은 50~120자 (약 5~10초 분량)
|
||||
- 각 단계 나레이션은 30~80자 (약 3~7초 분량)
|
||||
|
||||
반드시 아래 JSON 형식으로만 응답하세요:
|
||||
{
|
||||
"screen_number": {$screenNumber},
|
||||
"title": "이 화면의 제목 (10자 이내)",
|
||||
"narration": "사용자 안내 나레이션 (50~120자)",
|
||||
"ui_elements": [
|
||||
"steps": [
|
||||
{
|
||||
"type": "button|input|table|menu|tab|label|icon|other",
|
||||
"label": "UI 요소에 표시된 텍스트",
|
||||
"x": 0.5,
|
||||
"y": 0.3,
|
||||
"description": "이 요소의 기능 설명 (20자 이내)"
|
||||
"step_number": 1,
|
||||
"narration": "이 단계에서 할 일을 안내하는 나레이션 (30~80자)",
|
||||
"focused_element": {
|
||||
"type": "button|input|table|menu|tab|sidebar|header|form|other",
|
||||
"label": "UI 영역에 표시된 텍스트 (10자 이내)",
|
||||
"x": 0.1,
|
||||
"y": 0.3,
|
||||
"w": 0.2,
|
||||
"h": 0.4
|
||||
},
|
||||
"duration": 6
|
||||
}
|
||||
],
|
||||
"duration": 8
|
||||
]
|
||||
}
|
||||
|
||||
ui_elements의 x, y는 화면 좌상단(0,0) ~ 우하단(1,1) 기준 비율 좌표입니다.
|
||||
duration은 이 화면을 보여줄 권장 시간(초)입니다 (5~12초).
|
||||
=== focused_element 좌표 설명 ===
|
||||
- x, y: 영역의 좌상단 꼭짓점 위치 (화면 비율 0~1)
|
||||
- w, h: 영역의 너비와 높이 (화면 비율 0~1)
|
||||
- 예) 왼쪽 사이드바: x=0, y=0.1, w=0.15, h=0.8
|
||||
- 예) 상단 헤더: x=0, y=0, w=1.0, h=0.08
|
||||
- 예) 메인 테이블: x=0.2, y=0.2, w=0.7, h=0.6
|
||||
|
||||
=== duration 규칙 ===
|
||||
- 각 단계는 5~8초 (나레이션 길이에 비례)
|
||||
- 전체 steps의 duration 합계가 20~40초가 되도록 조절
|
||||
PROMPT;
|
||||
|
||||
$parts = [
|
||||
|
||||
Reference in New Issue
Block a user