Files
sam-manage/app/Services/Video/ScreenAnalysisService.php
김보곤 768bc30a6d feat:사용자 매뉴얼 영상 자동 생성 기능 구현
- TutorialVideo 모델 (상태 관리, TenantScope)
- GeminiScriptService에 callGeminiWithParts() 멀티모달 지원 추가
- ScreenAnalysisService: Gemini Vision 스크린샷 AI 분석
- SlideAnnotationService: PHP GD 이미지 어노테이션 (마커, 캡션)
- TutorialAssemblyService: FFmpeg 이미지→영상 합성 (crossfade)
- TutorialVideoJob: 분석→슬라이드→TTS→BGM→합성 파이프라인
- TutorialVideoController: 업로드/분석/생성/상태/다운로드/이력 API
- React-in-Blade UI: 3단계 (업로드→분석확인→생성모니터링) + 이력

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:56:39 +09:00

137 lines
4.2 KiB
PHP

<?php
namespace App\Services\Video;
use Illuminate\Support\Facades\Log;
class ScreenAnalysisService
{
private GeminiScriptService $gemini;
public function __construct(GeminiScriptService $gemini)
{
$this->gemini = $gemini;
}
/**
* 스크린샷 배열을 Gemini Vision으로 분석
*
* @param array $imagePaths 스크린샷 파일 경로 배열
* @return array 분석 결과 배열
*/
public function analyzeScreenshots(array $imagePaths): array
{
$results = [];
foreach ($imagePaths as $index => $imagePath) {
$screenNumber = $index + 1;
Log::info("ScreenAnalysis: 스크린샷 {$screenNumber}/" . count($imagePaths) . " 분석 시작", [
'path' => $imagePath,
]);
$result = $this->analyzeSingleScreen($imagePath, $screenNumber, count($imagePaths));
if ($result) {
$results[] = $result;
} else {
Log::warning("ScreenAnalysis: 스크린샷 {$screenNumber} 분석 실패, 기본값 사용");
$results[] = [
'screen_number' => $screenNumber,
'title' => "화면 {$screenNumber}",
'narration' => "이 화면에서는 주요 기능을 확인할 수 있습니다.",
'ui_elements' => [],
'duration' => 8,
];
}
}
return $results;
}
/**
* 단일 스크린샷 분석
*/
private function analyzeSingleScreen(string $imagePath, int $screenNumber, int $totalScreens): ?array
{
if (! file_exists($imagePath)) {
Log::error("ScreenAnalysis: 파일 없음 - {$imagePath}");
return null;
}
$imageData = base64_encode(file_get_contents($imagePath));
$mimeType = mime_content_type($imagePath) ?: 'image/png';
$prompt = <<<PROMPT
당신은 SAM(Smart Automation Management) 시스템의 사용자 매뉴얼 작성 전문가입니다.
이 스크린샷은 SAM 시스템의 화면입니다. (화면 {$screenNumber}/{$totalScreens})
이 화면을 분석하고 사용자 튜토리얼 나레이션을 작성하세요.
=== 분석 요구사항 ===
1. 화면의 주요 목적/기능을 파악
2. 주요 UI 요소 식별 (버튼, 입력폼, 테이블, 메뉴, 탭 등)
3. 각 UI 요소의 대략적인 위치를 화면 비율(0~1)로 표시
4. 사용자가 이 화면에서 수행할 작업 순서를 안내하는 나레이션 작성
=== 나레이션 작성 규칙 ===
- 친근한 존댓말 사용 (예: "~하실 수 있습니다", "~을 클릭하세요")
- TTS로 읽을 것이므로 이모지/특수기호 금지
- 순수 한글 텍스트만 작성
- 문장은 마침표로 끝내기
- 전체 나레이션은 50~120자 (약 5~10초 분량)
반드시 아래 JSON 형식으로만 응답하세요:
{
"screen_number": {$screenNumber},
"title": "이 화면의 제목 (10자 이내)",
"narration": "사용자 안내 나레이션 (50~120자)",
"ui_elements": [
{
"type": "button|input|table|menu|tab|label|icon|other",
"label": "UI 요소에 표시된 텍스트",
"x": 0.5,
"y": 0.3,
"description": "이 요소의 기능 설명 (20자 이내)"
}
],
"duration": 8
}
ui_elements의 x, y는 화면 좌상단(0,0) ~ 우하단(1,1) 기준 비율 좌표입니다.
duration은 이 화면을 보여줄 권장 시간(초)입니다 (5~12초).
PROMPT;
$parts = [
['text' => $prompt],
[
'inlineData' => [
'mimeType' => $mimeType,
'data' => $imageData,
],
],
];
$result = $this->gemini->callGeminiWithParts($parts, 0.3, 2048);
if (! $result) {
return null;
}
$parsed = $this->gemini->parseJson($result);
if (! $parsed || ! isset($parsed['screen_number'])) {
Log::warning('ScreenAnalysis: JSON 파싱 실패 또는 형식 불일치', [
'result' => substr($result, 0, 300),
]);
return null;
}
// screen_number 강제 보정
$parsed['screen_number'] = $screenNumber;
return $parsed;
}
}