# task-673.1 완료 보고서

## SCQA

**S**: ThreadAuto 영상 파이프라인이 render_all_scenes()에서 ~1200프레임을 list에 누적하여 7.1GB RAM을 사용하며, 서버(RAM 7.7GB)에서 OOM으로 다운되는 상황이다.

**C**: 프레임을 메모리에 모두 적재한 후 ImageSequenceClip으로 인코딩하는 구조가 원인이다. 또한 나레이션이 화면 텍스트를 그대로 읽는 형태이며, 텍스트 크기가 가독성에 부족하다.

**Q**: FFmpeg 파이프 스트리밍으로 RAM 사용을 수십MB로 줄이고, 나레이션-화면텍스트 분리 + 텍스트 1.3배 확대를 반영하여 Threads에 영상을 업로드할 수 있는가?

**A**: render_scene/render_all_scenes를 generator로 변환하고 3개 파일(evan_dynamic.py, pipeline_orchestrator.py, tts_sync.py)에서 ImageSequenceClip을 FFmpeg subprocess pipe로 교체하여 메모리 사용을 프레임 1장(5.9MB) + FFmpeg 버퍼 수준으로 절감했다. 텍스트 크기 1.3배 적용, 나레이션-화면텍스트 분리 확인 후 Threads 업로드 성공(게시물 ID: 18098920145300086). pytest 32건 전체 통과, pyright 에러 0건.

---

## 작업 내용

### 수정 1: FFmpeg 파이프 스트리밍 (RAM 문제 해결)

**A. evan_dynamic.py**
- `render_scene()`: list 누적 → yield generator로 변환 (반환타입 `Iterator[np.ndarray]`)
- `render_all_scenes()`: list extend → `yield from` generator로 변환
- `generate_evan_dynamic_video()`: `ImageSequenceClip` → FFmpeg subprocess.Popen + stdin pipe
- `from moviepy import ImageSequenceClip` 삭제

**B. video/pipeline_orchestrator.py**
- `generate_shortform_video()` 6-7단계: `ImageSequenceClip` → FFmpeg subprocess pipe
- `from moviepy import ImageSequenceClip` 삭제, `import subprocess` 추가

**C. video/tts_sync.py**
- `render_with_audio()`: `ImageSequenceClip` → FFmpeg subprocess pipe
- `from moviepy import ImageSequenceClip` 삭제
- 썸네일 로직: `list(renderer.render_scene(scenes[0]))[-1]`로 수정 (generator 대응)

### 수정 2: 나레이션-화면텍스트 분리
- **이미 구현 완료 확인**: `03_writing.md`에 narration 규칙 존재 (line 144-151)
- `extract_narration_texts()`가 narration 필드를 우선 사용 (line 73-76)
- 화면 렌더링은 기존대로 title/description 사용

### 수정 3: 텍스트 크기 1.3배
- `generate_scenes_from_slides()`: cover(94→122, 68→88), card_list(62→81, 49→64), detail(62→81, 42→55, 62→81), cta(73→95, 104→135, 47→61, 36→47), body(73→95, 52→68)
- `generate_insurance_scenes()`: 동일 비율 적용 (Scene 1~5 전체)

### 수정 4: 콘텐츠 정체성 규칙
- **이미 반영 완료 확인**: `03_writing.md` line 170-185에 조직 구조 정확성 규칙 존재

### Threads 업로드
- 토픽: 연금저축 (보험/연금 도메인)
- Threads 게시물 ID: `18098920145300086`
- 영상 크기: 1.5 MB
- 업로드 성공 확인

---

## 생성/수정 파일 목록

- `/home/jay/projects/ThreadAuto/video/evan_dynamic.py` — generator 변환, FFmpeg pipe, font size 1.3x
- `/home/jay/projects/ThreadAuto/video/pipeline_orchestrator.py` — FFmpeg pipe, moviepy import 제거
- `/home/jay/projects/ThreadAuto/video/tts_sync.py` — FFmpeg pipe, moviepy import 제거
- `/home/jay/projects/ThreadAuto/video/tests/test_tts_sync.py` — ImageSequenceClip mock → subprocess.Popen mock
- `/home/jay/projects/ThreadAuto/tests/test_task668.py` — font_size 기대값 1.3배로 업데이트

---

## 테스트 결과

- pytest: 32 passed, 0 failed (video/tests/test_pipeline_orchestrator.py + video/tests/test_tts_sync.py + tests/test_task668.py)
- pyright: 0 errors, 0 warnings, 0 informations
- black + isort: 5 files reformatted

---

## 발견 이슈 및 해결

### 자체 해결 (3건)

1. **pyright `proc.stdin` Optional 에러** — 3개 파일에 `assert proc.stdin is not None` 추가
   - 상세: subprocess.Popen에 `stdin=subprocess.PIPE` 전달 시 stdin은 항상 non-None이지만, 타입 시스템은 Optional로 인식. assert로 해결.

2. **test_tts_sync.py mock 호환성 깨짐** — ImageSequenceClip mock → subprocess.Popen mock으로 교체
   - 상세: tts_sync.py에서 moviepy import 삭제 후 테스트가 존재하지 않는 속성을 mock하려 해서 실패. subprocess.Popen mock으로 변경.

3. **test_task668.py font_size 기대값 불일치** — 기대값을 1.3배 새 값으로 업데이트
   - 상세: cover title 94→122 등 전체 font_size 기대값을 새 값으로 갱신.

---

## QC 셀프 체크리스트

- [x] 1. 영향 파일: evan_dynamic.py, pipeline_orchestrator.py, tts_sync.py, 테스트 2건
- [x] 2. 엣지 케이스: narration 없는 legacy 슬라이드 → fallback 동작 확인 (기존 로직 유지), generator 소진 후 썸네일 → list()로 별도 렌더링
- [x] 3. 작업 지시 일치: 4개 수정사항 + Threads 업로드 모두 완료
- [x] 4. 에러 처리: FFmpeg 프로세스 실패 시 proc.wait()로 감지, assert로 타입 안전성 확보
- [x] 5. 테스트 커버리지: 32건 전체 통과, 회귀 0건
- [x] 6. 발견 이슈 모두 직접 해결 (3건)

---

## QC 자동 검증 결과

```
overall: PASS (6 PASS, 4 SKIP)
- file_check: PASS (5개 파일 존재 + 크기 확인)
- data_integrity: PASS
- tdd_check: PASS (테스트 2개 + 구현 3개 확인)
- pyright_check: PASS (0 errors, 0 warnings)
- style_check: PASS (black OK, isort OK)
- critical_gap: PASS
```

작업 소요 시간: 15분 14초
