공정 엔지니어의 AI 엔지니어로의 성장 기록

비전공자의 바이브 코딩/일상 & 기초

유튜브 AI 요약기를 1시간 만에 만들었다— 편향 감지 + 카테고리 분류 + 히스토리, API 비용 0원

ai-process-engineer 2026. 6. 14. 17:11
유튜브 AI 요약기를 1시간 만에 만들었다 — Ollama 완전 무료, 편향 감지까지

유튜브 AI 요약기를 1시간 만에 만들었다
— 편향 감지 + 카테고리 분류 + 히스토리, API 비용 0원

유튜브 영상 하나를 제대로 보려면 최소 10~30분이다. 내용이 괜찮은지 확인하려고 영상을 전부 보고 나서야 "시간 낭비였다"고 깨닫는 경험, 누구나 있을 것이다. URL 하나 붙여넣으면 3줄로 요약해주고, 카테고리를 자동 분류하고, 편향된 영상은 경고까지 띄워주는 도구가 있으면 어떨까. 그걸 Claude Code로 1시간 만에 만들었다.

기술 스택: Next.js 14 + TypeScript (Frontend) / Python FastAPI (Backend) / Ollama gemma3:4b (LLM) / youtube-transcript-api (자막 추출) — 외부 API 키 없음, 비용 0원

01 먼저 결과물부터

말보다 화면을 먼저 보는 게 낫다. 아래는 실제 동작 화면이다.

메인 화면 — URL 입력창 + 최근 히스토리 목록
분석 중 — 자막 추출 → AI 분석 → 저장 3단계 로딩
요약 결과 — 3줄 요약 + 카테고리 뱃지 + 키워드
히스토리 페이지 — 카테고리 필터 + 전체 목록

URL 하나 넣으면 10~30초 안에 요약이 완료된다. 분석된 영상은 자동으로 히스토리에 쌓이고, 카테고리별로 필터링해서 볼 수 있다.

02 핵심 기능 4가지

  • 자막 기반 AI 요약 youtube-transcript-api로 자막을 추출하고 Ollama(gemma3:4b)가 3줄로 압축한다. YouTube Data API 키 없이 동작하고, 한국어 자막이 없으면 영어 자막으로 자동 전환한다.
  • 카테고리 자동 분류 + 히스토리 누적 기술 / 경제·비즈니스 / 과학 / 교육 / 엔터테인먼트 / 뉴스·시사 / 기타 7개 카테고리로 자동 분류한다. 분석 결과는 history.json 파일에 자동 저장되어 누적되고, 카테고리별 필터링이 가능하다. DB 없이 JSON 파일 하나로 해결했다.
  • 편향 감지 (3단계 경고) — 현재 최적화 필요 자막 내용을 분석해 일방적 주장이나 확인되지 않은 정보가 포함된 영상에 경고를 표시한다. "가짜뉴스 확정"이 아니라 주의 권고 수준이며, AI 오판 가능성을 UI에 명시해두었다. 다만 gemma3:4b 기준으로 판단 정확도가 불안정하여 대부분의 영상이 🟢 normal로 분류되는 경향이 있다. 프롬프트 튜닝 또는 gemma3:12b 이상 모델로 전환하면 개선될 것으로 보이며, 추후 포스팅에서 최적화 과정을 다룰 예정이다.
  • 완전 무료 — 로컬 LLM 사용 OpenAI, Anthropic 등 외부 API를 전혀 사용하지 않는다. Ollama를 로컬에 설치하고 gemma3:4b 모델(약 3GB)을 다운받으면 이후 비용은 0원이다. RTX 3060 기준 응답 10~30초.
🟢
일반
특이사항 없는 일반 영상
🟡
주의
일방적 주장 포함 가능, 감정적 표현 많음
🔴
경고
확인되지 않은 정보, 극단적 표현 포함 가능

03 동작 구조

전체 흐름은 단순하다. 자막만 가져올 수 있으면 나머지는 LLM이 전부 처리한다.

유튜브 URL 입력
자막 추출
youtube-transcript-api
Ollama gemma3:4b
요약 + 분류 + 편향
결과 표시
history.json 저장

핵심은 자막 → LLM 프롬프트 한 번으로 요약, 카테고리, 키워드, 편향 판단을 동시에 처리한다는 점이다. API 호출을 4번 하는 게 아니라 JSON 형식으로 한 번에 받는다.

Ollama 프롬프트 구조
# 자막 텍스트를 넣으면 아래 JSON을 한 번에 반환
{
  "title": "자막에서 추론한 영상 제목",
  "summary": ["핵심1", "핵심2", "핵심3"],
  "category": "기술",
  "keywords": ["AI", "자동화", "Python"],
  "bias_level": "normal",   // normal / caution / warning
  "bias_reason": ""
}
트릭 하나 — 유튜브 썸네일은 img.youtube.com/vi/{video_id}/maxresdefault.jpg 패턴으로 YouTube Data API 없이 바로 가져온다. 영상 제목도 LLM이 자막 내용을 보고 추론한다. 외부 API 의존성이 youtube-transcript-api 하나뿐이라 설치가 극도로 단순하다.

04 기술 스택

구성기술역할
FrontendNext.js 14 + TypeScript + Tailwind단일 페이지 + 히스토리 페이지
BackendPython FastAPI + UvicornREST API, 자막 추출, LLM 호출
LLMOllama gemma3:4b요약 + 분류 + 편향 감지
자막youtube-transcript-api 1.2.4유튜브 자막 추출 (API 키 없음)
저장소history.json 파일DB 없이 JSON으로 히스토리 누적
외부 API 비용

0원
OpenAI, Anthropic, YouTube Data API 전혀 사용 안 함

필요한 것

Ollama 설치 + gemma3:4b 다운로드(3GB, 1회)
그 이후는 영구 무료

05 실행 방법

PRD를 Claude Code에 넣으면 알아서 만들어준다. 실행 전 Ollama 준비만 하면 된다.

  1. 01 Ollama 설치ollama.com에서 Windows 버전 다운로드 후 설치
  2. 02 모델 다운로드 — 최초 1회만. 이후엔 로컬에서 무료 실행
  3. 03 Ollama 서버 실행ollama serve
  4. 04 Start.bat 더블클릭 — 백엔드(8000) + 프론트(3000) 동시 실행
  5. 05 브라우저에서 접속http://localhost:3000
bash
# 1. 모델 다운로드 (약 3GB, 최초 1회)
ollama pull gemma3:4b

# 2. Ollama 서버 실행
ollama serve

# 3. 프로젝트 실행 (Start.bat 더블클릭 또는 수동)
# 백엔드
cd src/backend
uvicorn main:app --reload --port 8000

# 프론트엔드
cd src/frontend
npm run dev    # next dev --turbo (Windows 파일 잠금 오류 방지)
모델 업그레이드 — 품질을 높이고 싶으면 ollama pull gemma3:12b.env에서 OLLAMA_MODEL=gemma3:12b로 바꾸면 된다. RTX 3060 12GB에서 충분히 동작한다.

06 1시간 완성의 비결 — 코드가 아니라 PRD다

이 앱을 1시간 만에 만든 비결은 코딩 실력이 아니다. PRD를 충분히 잘 만들었기 때문이다. Claude Code에게 "유튜브 요약기 만들어줘"라고 하면 방향을 잃지만, PRD를 주면 기계처럼 실행한다. 폴더 구조부터 API 엔드포인트, 컴포넌트 분리, 에러 처리까지 알아서 만든다.

코드를 공유하는 대신 PRD를 공유한다. 코드는 라이브러리 버전이 바뀌면 금방 outdated되지만 PRD는 영원히 유효하다. 아래 PRD를 그대로 Claude Code에 붙여넣으면 동일한 서비스를 누구나 만들 수 있다.

사용 방법 — 아래 PRD 전체 복사 → PRD.md로 저장 → Claude Code 실행 → "PRD.md를 읽고 Phase 1부터 순서대로 만들어줘" 입력. 끝.
📄 PRD — YouTube AI Curator
# PRD v2 — YouTube AI Curator
> Claude Code(바이브코딩)로 만드는 유튜브 영상 AI 요약기
> Ollama(gemma3:4b) 로컬 LLM — API 비용 0원, 완전 무료
> 요약 + 카테고리 + 편향 감지 + 히스토리 관리

---

## 1. 프로젝트 개요

| 항목 | 내용 |
|------|------|
| 프로젝트명 | YouTube AI Curator |
| 목적 | 유튜브 URL → 자막 추출 → 요약 + 카테고리 + 편향 감지 + 히스토리 |
| 개발 방식 | Claude Code 바이브코딩 |
| 개발 환경 | Windows 11 |
| 핵심 원칙 | API 비용 0원 / DB 없음 / 설치 최소화 |

---

## 2. 기술 스택

### Frontend
- Framework: Next.js 14 App Router + TypeScript strict
- Styling: Tailwind CSS v3
- 상태관리: React useState (외부 라이브러리 없음)
- Dev 서버: next dev --turbo (Windows 파일 잠금 오류 방지)

### Backend
- Runtime: Python 3.11+
- Framework: FastAPI + Uvicorn
- 자막 추출: youtube-transcript-api 1.x (API 키 불필요)
- LLM 호출: Ollama REST API (http://localhost:11434)
- 패키지: requirements.txt (>= 버전 지정)

### LLM
- 엔진: Ollama (로컬 실행)
- 기본 모델: gemma3:4b
- 변경: ollama pull gemma3:12b 후 .env에서 OLLAMA_MODEL=gemma3:12b

### 저장소
- JSON 파일 (src/backend/history.json) — DB 없이 파일로 누적 저장
- 파일 없으면 자동 생성, 최신순(index 0) 저장

### 환경변수 (src/backend/.env)
OLLAMA_HOST=http://localhost:11434
OLLAMA_MODEL=gemma3:4b
HISTORY_FILE=history.json

---

## 3. 핵심 기능

### 3-1. 유튜브 URL 입력
- 지원: youtube.com/watch?v=xxx / youtu.be/xxx / youtube.com/embed/xxx
- 자막 없는 영상 → 에러 메시지

### 3-2. 자막 추출
- youtube-transcript-api 1.x 로 추출
- 한국어(ko) 우선 → 영어(en) → 가능한 첫 번째 언어 자동 폴백
- 특수 공백 문자(\xa0, \u200b) 정규화
- 4000자 제한 (gemma3:4b 컨텍스트 대응)

### 3-3. AI 분석 (한 번에 처리)
Ollama(gemma3:4b)가 아래를 JSON 하나로 반환:
- 영상 제목 추론 (YouTube API 없이 자막에서 LLM이 생성)
- 3줄 요약
- 카테고리: 기술/경제·비즈니스/과학/교육/엔터테인먼트/뉴스·시사/기타
- 핵심 키워드 5개 이내
- 편향 등급: normal/caution/warning

썸네일: img.youtube.com/vi/{video_id}/maxresdefault.jpg (API 불필요)

### 3-4. 편향 감지
- normal: 특이사항 없음
- caution: 일방적 주장 포함 가능 (🟡 노란 뱃지)
- warning: 확인되지 않은 정보 포함 가능 (🔴 빨간 뱃지)
- UI에 "AI 판단이므로 참고용" 안내 문구 필수

### 3-5. 히스토리 누적 저장
- 요약 완료 시 history.json에 자동 append
- 저장 항목: id(uuid), url, title, thumbnail, category, keywords,
             summary, bias, saved_at
- 히스토리 페이지: 카테고리 필터 + 목록 + 개별 삭제

### 3-6. 로딩 단계
자막 추출 중 → AI 분석 중 (10~30초) → 저장 중

---

## 4. 폴더 구조

src/
├── frontend/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── page.tsx            # 메인 (URL 입력 + 결과 + 히스토리 미리보기)
│   │   └── history/page.tsx    # 히스토리 전체 목록
│   ├── components/
│   │   ├── UrlInput.tsx
│   │   ├── SummaryResult.tsx   # 편향 뱃지 포함
│   │   ├── BiasBadge.tsx       # 🟢🟡🔴
│   │   ├── HistoryList.tsx     # 카테고리 필터 + 삭제
│   │   ├── HistoryCard.tsx
│   │   ├── LoadingStatus.tsx   # 3단계
│   │   └── ErrorMessage.tsx
│   └── lib/
│       ├── api.ts
│       └── types.ts
│
└── backend/
    ├── main.py
    ├── schemas.py
    ├── routers/
    │   ├── summarize.py        # POST /api/summarize
    │   └── history.py          # GET, DELETE /api/history
    ├── services/
    │   ├── transcript.py       # youtube-transcript-api 1.x 래퍼
    │   ├── ollama.py           # Ollama 호출 + JSON 파싱
    │   └── storage.py          # history.json 읽기/쓰기/삭제
    ├── requirements.txt
    ├── .env
    └── .env.example

---

## 5. API 엔드포인트

POST /api/summarize
요청: { "url": "https://www.youtube.com/watch?v=xxxxx" }
응답: { id, title, thumbnail, url, summary[], category,
        keywords[], bias{level,label,emoji}, transcript_length, saved_at }

GET  /api/history?category=기술&bias=caution
응답: { total, items[] }

DELETE /api/history/{id}

에러 응답: { "error": "no_transcript|ollama_unavailable|parse_error",
             "message": "..." }

---

## 6. 서비스 상세

### transcript.py (youtube-transcript-api 1.x 필수 주의)
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api._errors import NoTranscriptFound

_api = YouTubeTranscriptApi()   # v1.x: 인스턴스 생성 필수

def get_transcript(url):
    video_id = extract_video_id(url)
    try:
        fetched = _api.fetch(video_id, languages=["ko", "en"])
    except NoTranscriptFound:
        fetched = _api.fetch(video_id)   # 언어 무관 폴백
    text = " ".join(s.text for s in fetched)
    text = text.replace("\xa0", " ").replace("\u200b", "")
    return text[:4000]

### ollama.py
- /api/tags로 Ollama 상태 확인 (503 반환)
- 응답 JSON 파싱 시 마크다운 펜스 제거: re.sub(r'```json|```', '', text)
- 파싱 실패 시 1회 재시도

### storage.py
- Path(__file__).parent.parent / os.getenv("HISTORY_FILE", "history.json")
- 파일 없으면 [] 자동 생성
- 읽기/쓰기 encoding="utf-8" 필수
- 새 항목 index 0 삽입 (최신순)

---

## 7. 개발 단계 (Phase)

Phase 1 — 백엔드 코어
- FastAPI 초기화 + CORS
- transcript.py (youtube-transcript-api 1.x)
- ollama.py (편향 감지 프롬프트 포함)
- storage.py (history.json)
- POST /api/summarize, GET/DELETE /api/history

Phase 2 — 프론트엔드
- Next.js 14 + Tailwind
- UrlInput, LoadingStatus (3단계), BiasBadge
- SummaryResult, HistoryCard, HistoryList
- /history 페이지, FastAPI 연동

Phase 3 — 마무리
- Start.bat / Stop.bat (영어만, Ollama 실행 확인 포함)
- .env.example, README.md

---

## 8. 실행 전 준비

ollama pull gemma3:4b   # 약 3GB, 최초 1회
ollama serve

---

## 9. 주의사항

- youtube-transcript-api v1.x: 클래스 메서드 제거됨, 반드시 인스턴스 사용
- 자막 없는 영상 (라이브, 일부 저작권) 지원 불가
- gemma3:4b JSON 불안정 → 마크다운 펜스 제거 + 재시도 필수
- .bat 파일 한글 금지 (CP949 인코딩 오류)
- history.json은 .gitignore에 추가
- Start.bat는 프로젝트 루트에 위치 (%~dp0 기준 경로)
- 모델 업그레이드: ollama pull gemma3:12b → .env OLLAMA_MODEL=gemma3:12b

07 Claude Code로 만들다가 막힌 것들

PRD를 줬는데도 Claude Code가 틀리는 부분들이 있었다. 실제로 겪은 이슈를 정리한다.

  • youtube-transcript-api 1.x API 변경 Claude Code가 구버전 클래스 메서드 방식으로 코드를 짰다. YouTubeTranscriptApi.list_transcripts()는 1.0.0부터 제거됐고 YouTubeTranscriptApi().fetch() 인스턴스 방식으로 바뀌었다. 학습 데이터 컷오프 문제다. CLAUDE.md에 명시해두면 해결된다.
  • 자막 내 특수 공백 문자 일부 영상에서 \xa0 (비깨짐 공백), \u200b (폭없는 공백)이 자막에 포함되어 Windows CP949 환경에서 인코딩 오류가 났다. 자막 추출 후 정규화 처리를 넣으면 해결된다.
  • Start.bat ollama list 파이프 hang ollama list | findstr "gemma3:4b"가 특정 환경에서 무한 대기 상태에 빠졌다. 모델 확인 블록 전체를 제거하고 Ollama 서버 실행 여부만 curl로 체크하는 방식으로 바꿨다.
  • gemma3:4b JSON 마크다운 펜스 LLM이 JSON 앞뒤에 ```json ... ```를 붙이는 경우가 많다. 파싱 전 반드시 제거해야 하고, 실패 시 1회 재시도 로직도 넣어야 한다.
  • 편향 감지 정확도 부족 — 추후 최적화 예정 gemma3:4b는 4B 파라미터 소형 모델이라 편향 판단 능력이 제한적이다. 실제 테스트에서 대부분의 영상이 🟢 normal로 분류되어 경고 기능이 사실상 동작하지 않는 경우가 많았다. 프롬프트를 더 구체적으로 튜닝하거나 gemma3:12b 이상의 모델로 전환하는 방향으로 개선할 예정이다. 현재는 참고용 수준으로만 활용을 권장한다.

08 이 구조로 더 할 수 있는 것들

자막 추출 + LLM 호출 구조 하나로 프롬프트만 바꾸면 다양한 기능을 추가할 수 있다.

  • 영어 영상 한국어 요약 영어 자막을 가져와서 "한국어로 요약해줘" 한 줄만 추가하면 된다. 이미 이 구조에서 동작한다.
  • 챕터별 타임스탬프 정리 자막에 타임스탬프 정보도 포함되어 있다. "10분 단위로 챕터를 나눠줘" 프롬프트를 추가하면 된다.
  • 블로그 초안 자동 생성 "이 영상 내용으로 블로그 포스트 초안을 작성해줘" — 자막 길이만 충분하면 gemma3:12b 이상에서 품질 좋은 초안이 나온다.
  • 카카오톡 / 텔레그램 알림 연동 요약 완료 시 모바일로 푸시 알림을 보내면 브라우저 안 열어도 된다. 기존 카카오톡 연동 포스팅(시리즈 #5 참고)과 같은 구조로 붙일 수 있다.
Summary

Claude Code로 유튜브 AI 요약기를 1시간 만에 완성했다. URL 하나로 요약 + 카테고리 자동 분류 + 편향 감지 + 히스토리 누적까지 동작하고, Ollama gemma3:4b 로컬 LLM을 써서 API 비용은 0원이다. 코드가 아니라 PRD를 공유하는 이유는 하나다 — PRD를 그대로 Claude Code에 넣으면 누구나 동일한 서비스를 1시간 안에 만들 수 있기 때문이다. 위 PRD를 복사해서 바로 시작해보자.