영상을 보기만 하던 시간을, 아이가 손으로 무언가를 하는 시간으로 바꾼다.
만 3–7세용 안드로이드 태블릿 앱과, 그 앱이 낼 활동을 애니메이션 한 편에서 뽑아내는 파이프라인이 함께 들어 있다. 재생 중에는 모델을 호출하지 않는다 — 아이가 만나는 문항과 개입 지점은 전부 미리 만들어져 기기 안에 있다.
SKT FLY AI 열정4팀 최종 프로젝트.
| 앱 코드 (JS) | 12,022줄 |
| 서버·도구 (Node) | 2,075줄 |
| 화면 | 14종 |
| DB 테이블 | 9개 |
| 파이프라인 외부 패키지 | 0개 |
frontend/ Expo React Native 앱 (안드로이드 태블릿)
pipeline/ 영상 → 개입지점 + 활동 ← 파이프라인 정본
backend/
server/ 콘텐츠·기록 API (Node 내장 http + node:sqlite)
worker/ 그림 변환 중계 (Cloudflare Worker → fal.ai)
tools/ 라인아트 준비 등 보조 스크립트
docs/ 기획서 · 파이프라인 설계 · 발표 부록
파이프라인 이야기는 pipeline/README.md가 정본이다. 이 문서는 두 갈래가 어떻게 맞물리는지와 앱 쪽을 다룬다.
영상.mp4
│ pipeline/storydot.py 받아쓰기 2패스 · 화면 분석 · 개입지점 결정
│ pipeline/generate.py 활동 생성 → 결정적 검증 게이트
▼
work/<작품>_final.json 문항 · 개입 시각 · 근거 프레임
│ backend/server/tools/seed-from-work.js
▼
stary.db + media/ 콘텐츠 서버 :5056
│ GET /plan 하루 시간 예산 안에서 영상·활동·미션 편성
▼
frontend 시청 루프 → 개입 → 문항 → 그리기 → 리포트
서버가 대답하지 않으면 앱은 함께 나간 스냅숏(assets/library.json · activities.json ·
missions.json)과 기기의 media 폴더를 읽는다. 스키마가 같아서 분기 한 번으로 대체된다.
시연은 이 경로로 돈다 — 노트북이 필요 없다.
Node 22 이상이 필요하다. 서버가 node:sqlite 내장 모듈을 쓴다.
활동 생성에는 claude CLI가 필요하다 — 생성자와
검증자를 별도 프로세스로 띄우는 데 쓴다. 없으면 개입 지점까지(storydot.py)만 돈다.
파이프라인 — API 키가 필요 없다.
cd pipeline
./setup.sh # ffmpeg · whisper.cpp · 모델 2GB (최초 1회)
python3 storydot.py ~/Downloads/타요1화.mp4 # 개입지점 + 증거번들
python3 generate.py work/타요1화_plan.json # 활동 생성 · 검증
python3 generate3.py 타요1화 # 마무리 활동서버 — 의존성이 없다.
node backend/server/index.js # :5056, DB는 backend/server/data/stary.db 에 생긴다앱
cd frontend && npm install && npx expo start --dev-client
adb reverse tcp:8081 tcp:8081
adb reverse tcp:5056 tcp:5056Skia와 Reanimated를 쓰므로 Expo Go로는 열리지 않는다. 개발용 빌드를 한 번 설치해야 한다.
cd frontend
npx eas build -p android --profile development # 개발용 — Metro 에 붙는다
cd android # 시연용 APK
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
./gradlew assembleRelease
adb install -r app/build/outputs/apk/release/app-release.apk영상은 저장소에 없다. 용량이 크고 저작권이 있는 파일이라 .gitignore 가 막는다. 두 경로 중
하나로 직접 넣는다.
# ① 파이프라인을 돌리지 않고 영상만 — inbox/<시리즈>/ 에 mp4 를 두고
node backend/server/tools/add-videos.js
# ② 파이프라인 결과와 함께 — work/ 의 산출물을 DB 로, 영상은 --video-dir 에서 찾는다
node backend/server/tools/seed-from-work.js work --video-dir ~/Downloads원본 mp4 가 아직 없으면 --placeholder <아무.mp4> 로 배선만 먼저 확인할 수 있다. 길이는
파이프라인이 실측한 값을 쓰므로 편성 계산은 진짜고, 재생되는 화면만 대역이다.
기기에서 서버 없이 돌릴 때는 앱과 함께 나가는 스냅숏(frontend/assets/library.json ·
activities.json)과 기기의 media 폴더를 읽는다. 스냅숏은 저장소에 들어 있다.
아이가 지나는 길은 여섯 단계다. 프로필 → 오늘의 묶음 → 시청과 개입 → 종료 문항 → 그리기와 변환 → 리포트.
재생 위치를 0.35초마다 살피다 개입 시각에 닿으면 3초를 세고 화면이 언다. 활동이 끝나면 멈춘 자리에서 이어진다. 중간 개입은 편당 1회로 제한한다 — 시청 흐름이 끊기는 비용이 개입 효과를 넘지 않게. 문항은 편이 끝난 뒤로 미룬다.
| 활동 | 형식 | 판정 근거 |
|---|---|---|
| 퍼즐 | 정지 프레임에서 오려낸 조각 끼우기 | 좌표 정합 — 최근접 빈자리 |
| 국어 | 3지선다 — 인물의 동기 · 대사의 의도 | 작품별 문항 뱅크, 오답도 그 편의 실재 요소 |
| 수학 | 3지선다 — 5개 유형, 근거 프레임 동반 | 화면 계수값과 대조 |
| 그리기 · 변환 | 자유 그리기 후 캐릭터로 변환 | 판정 없음 — 산출물 자체가 결과 |
| 단어장 | 낱말을 끌어 아는 것 · 모르는 것으로 분류 | 아이의 자기 보고 — 보호자 리포트에 집계 |
| 따라 쓰기 · 색칠 | 안내선 위 필기 · 선 내부 채우기 | 획이 안내선에 얹힌 비율 |
| 캐릭터 방 | 먹이기 · 쓰다듬기 · 던지기 | 정답 수가 보유 사탕으로 환산 |
실패 상태를 두지 않는다. 따라 쓰기·색칠은 아이가 멈추면 버디가 마저 하고, 문항은 오답이어도 다시 고를 수 있다. 활동이 하루의 진행을 막지 않는다.
퍼즐은 그리지 않는다. 개입 지점의 프레임에서 대상만 오려낸다. 조각과 결손부가 같은 베지어 윤곽을 쓰고, 2배로 오려 축소해 가장자리 계단을 없앤다. 판은 영상이 앉아 있던 칸을 그대로 메운다 — 여백이 생기면 영상에서 퍼즐로 넘어온 티가 난다.
입력은 지점 수로 갈린다. 한 손가락은 언제나 펜, 두 손가락은 확대·이동이라 그리다가 화면이 밀리지 않는다. 펜이 닿으면 잠깐 손가락 입력을 무시해 손바닥이 눌려도 선이 그어지지 않고, S펜 버튼을 누른 채 그으면 지우개가 된다.
보호자 리포트는 간단한 셈 문제를 풀어야 열린다. 하루 사용 시간은 5분 단위로 정하고, 활동을 종류별로 끌 수 있다.
두 가지를 정한다. 언제 끼어들 것인가와 무엇을 물을 것인가. 둘 다 순진한 해법이 실측에서 무너졌고, 그 결과가 코드의 형태를 정했다.
| 순진한 가정 | 실측 |
|---|---|
| 좋은 모델 하나면 된다 | large-v3-turbo 환각 23.5%, small 9.1% — 작은 모델이 2.6배 낫다 |
| VAD로 환각을 없앤다 | 환각은 0%가 되지만 실제 대사 30%가 사라진다 |
| 두 에이전트가 교차검증한다 | 세 패스가 같은 오답으로 수렴했다 |
| 조용해질 때 끼어든다 | BGM이 안 끊긴다. 762초에 1초 이상 공백이 16개뿐 |
| 장면 전환을 임계값으로 잡는다 | 어두운 구간에서 2컷 vs 실제 39컷 |
| 오디오로 타이밍을 정한다 | 9개 지점 중 3개가 동작 한복판 |
생성자와 검증자는 claude CLI 별도 프로세스 두 개로 띄운다 — 같은 문맥을 공유하면
검증이 아니라 추인이 된다. 마지막 관문은 LLM 판단이 아니라 문자열 대조·픽셀 계산이다.
파이썬 외부 패키지가 0개다. 표준 라이브러리와 ffmpeg · whisper-cli만 쓴다. API 키도,
종량 과금도 없다. 근거와 재현 명령은 docs/PIPELINE_PLAN.md.
아이가 그린 그림이 그 작품 세계의 캐릭터가 되어 돌아온다. 앱에서 바깥으로 나가는 유일한 경로이며, 주제 종류에 따라 워커가 모델을 고른다.
| 구분 | 사물 그림 | 캐릭터 그림 |
|---|---|---|
| 모델 | fal-ai/flux-kontext/dev |
fal-ai/nano-banana/edit |
| 입력 선 | 원위치 보존 후 정리 | 형태 규칙이 선을 상회 |
| 아이 그림에서 계승 | 윤곽 전체 | 색 · 머리 모양 |
같은 문장으로 둘 다 그리면 한쪽이 망가진다. 소방차에 유효한 "선을 지켜라"가 캐릭터에서는 낙서를 낙서로 남긴다. 부정문("동물이 아님")은 확산 모델에서 그 대상을 오히려 불러내므로 전부 걷어내고 긍정 규칙만 적었다.
fal 키는 워커 시크릿에만 둔다. 앱에는 넣지 않는다 — APK는 뜯어볼 수 있다.
cd backend/worker && npx wrangler deploy| 변수 | 쓰는 곳 | 없으면 |
|---|---|---|
STARY_PORT |
콘텐츠 서버 포트 | 5056으로 뜬다 |
FAL_KEY |
워커 시크릿 — 그림 변환 | 변환만 실패한다 |
ANTHROPIC_API_KEY |
구 파이프라인 API 경로 | pipeline/은 없이 돈다 |
값이 든 .env는 커밋하지 않는다.
cd frontend && npm run check # 바인딩 없는 이름 · 정의 없는 스타일 · 활동 규칙
python3 -m pytest pipeline/oneshot/tests -v # ffmpeg·API 호출은 전부 모킹RN은 존재하지 않는 스타일 키를 조용히 무시하므로 두 번째 검사가 특히 필요하다. 반대로 필기 입력 · 영상 렌더링 · 그림 변환 결과는 검사 대상이 아니다. 에뮬레이터에서 재현되지 않아 실기기에서만 확인된다.
| 증상 | 원인 | 대응 |
|---|---|---|
| CMake 중단 | JDK 25가 제한된 메서드 호출을 막는다 | JAVA_HOME을 JDK 21로 |
| 번들 단계에서 멈춤 | 개발 서버가 8081을 잡고 있으면 릴리스 번들러가 포트를 못 얻는다 | 빌드 전 8081 해제 |
| 리소스 파싱 실패 | 동기화가 만든 * 2.xml 같은 중복 파일 |
빌드 전 find android node_modules -name "* [0-9].*" -delete |
| 설치 거부 | 로컬 빌드와 배포본의 서명 불일치 | 개발 클라이언트는 EAS 빌드로만 갱신 |
- 퍼즐 조각의 윤곽은 사람이 지정한다. 색으로 대상을 분리하는 도구는 있지만 어느 프레임의 무엇을 오릴지는 아직 손으로 정한다.
- 국어 문항은 작품별 뱅크에서 나온다. 수학처럼 편마다 생성되지 않는다.
- 말하기 활동은 음량만 본다.
-35dB문턱을 일정 시간 넘겼는지로 통과를 정하고, 무엇을 말했는지는 대조하지 않는다. - 활동 형식이 3지선다에 몰려 있다.
사건의_순서_파악같은 유형은 이 형식에 잘 맞지 않는다. - 학습 효과는 측정하지 않았다. 활동이 성립하는지까지만 확인했다.