영상 하나로 AI 더빙 + 자동 자막을 만들어 다국어 버전을 한 번에 내보내는 모바일 앱. Kotlin Multiplatform + Compose Multiplatform 으로 Android · iOS 단일 코드 베이스.
폴더·docs 는
vibi로 통일됐지만 코드 패키지 (com.vibi.*), RoomVibiDatabase,
이 앱은 vibi-bff (Kotlin/Ktor) 를 통해
자막 · 자동 더빙 · 음성 분리 · 렌더 파이프라인을 호출한다.
모든 외부 API (Perso AI · Gemini) 는 BFF 에서만 다루며, 클라이언트는 /api/v2 만 사용.
빌드 전에 BFF 가 동작 중이어야 한다 (기본 :8080).
| 도구 | 용도 | 비고 |
|---|---|---|
| JDK 21 | gradle 실행 | java -version 으로 확인 |
| Android SDK | Android 빌드 | Android Studio 설치 또는 cmdline-tools |
| Xcode 15+ | iOS 빌드 | App Store 또는 developer.apple.com |
| XcodeGen | iOS 프로젝트 생성 | brew install xcodegen |
| vibi-bff | API 백엔드 | 별도 repo, :8080 에서 실행 중이어야 함 |
저장소 루트에 local.properties 를 만들고 (gitignore 됨):
sdk.dir=/Users/<you>/Library/Android/sdk
BFF_BASE_URL=http://10.0.2.2:8080/BFF_BASE_URL 은 실행 타깃에 맞는 주소 를 써야 한다 — mock 인터셉터 없음:
| 타깃 | 주소 |
|---|---|
| Android 에뮬레이터 | http://10.0.2.2:8080/ |
| Android 실기기 | http://192.168.x.x:8080/ (맥 LAN IP) |
| iOS 시뮬레이터 | http://localhost:8080/ 또는 맥 LAN IP |
| iOS 실기기 | 맥 LAN IP |
cd iosApp
xcodegen generateiosApp/iosApp.xcodeproj 가 생성된다.
# shared 모듈 + Android APK
./gradlew :shared:build :cmp:assembleDebug --no-configuration-cache
# APK: cmp/build/outputs/apk/debug/cmp-debug.apk--no-configuration-cache 는 일부 KMP 태스크 호환을 위해 필수
(자세한 사항은 shared/.claude/skills/build.md).
# 1. KMP framework 컴파일 (Apple Silicon 시뮬레이터)
./gradlew :shared:compileKotlinIosSimulatorArm64 --no-configuration-cache
# 2. Xcode 에서 열기 → 시뮬레이터 실행
open iosApp/iosApp.xcodeprojiosApp/ 의 preBuildScripts 가 :shared:embedAndSignAppleFrameworkForXcode 를 자동 호출하므로
별도 수동 framework 임베드 불필요.
./gradlew :shared:testDebugUnitTest --no-configuration-cache
./gradlew :shared:allTests --no-configuration-cachevibi-mobile/
├── shared/ # :shared — KMP 비즈니스 로직
│ ├── commonMain/com/vibi/shared/
│ │ ├── domain/{model,repository,usecase}/
│ │ ├── data/{remote,repository,local/db}/ # Ktor Client + Room v19
│ │ ├── ui/{input,timeline,export,share}/ # ViewModel
│ │ └── di/ # Koin 모듈
│ ├── androidMain/ └── iosMain/ # 플랫폼별 actual
│ └── (commonTest 13개 + 마이그레이션 테스트)
├── cmp/ # :cmp — Compose Multiplatform UI
│ ├── commonMain/com/vibi/cmp/
│ │ ├── App.kt + ui/navigation/ # sealed-class NavHost
│ │ ├── ui/{input,timeline,export,share}/
│ │ └── platform/ # VideoPlayer / MediaPicker expect
│ ├── androidMain/ └── iosMain/ # Media3 / AVPlayer · PHPicker
└── iosApp/ # XcodeGen project.yml + Swift entry
핵심 화면: Input → Timeline (편집·sheet 군) → Export → Share.
시트: InsertSubtitle · AudioSeparation · RegenerateSubtitles · DetailEdit · ChatPanel.
타임라인 구간 선택은 별도 sheet 가 아니라 UnifiedTimelineBar 인라인 (28~56dp 바, segment/directive content strip + range fill + bracket 핸들 + 재생 marker).
| 기능 | 클라이언트 | BFF 엔드포인트 |
|---|---|---|
| 영상 업로드 | InputScreen + MediaPicker · VideoThumbnailExtractor |
(로컬 segment) |
| 자막 (수동 + 자동) | InsertSubtitleSheet · GenerateAutoSubtitlesUseCase |
POST /api/v2/subtitles (+ poll) |
| 자동 더빙 (영상 + BGM) | GenerateAutoDubUseCase (mediaType=VIDEO|AUDIO) |
POST /api/v2/autodub (+ poll) |
| 음원 분리 (영상 segment + BGM clip) | AudioSeparationSheet · onStartBgmSeparation |
POST /api/v2/separate (+ stem mix) |
| 구간 선택 | UnifiedTimelineBar (인라인) | (로컬 Room) |
| 음원 삽입 (파일 + 즉시 녹음) | AudioPicker / AudioRecorder (expect/actual) |
(로컬 BgmClip) |
| 채팅 어시스턴트 | ChatPanel · ChatToolDispatcher |
POST /api/v2/chat (Gemini function calling) |
- Perso AI — 음원 분리, STT, 번역, 자동 더빙
- Gemini — 자막 번역 + 채팅 function calling
API 키는 모두 BFF env 에만 보관.
shared/CLAUDE.md—:shared의 KMP 제약, 도메인/데이터 레이어 규약, 알려진 iOS 버그 패턴cmp/CLAUDE.md—:cmp의 Compose 규약 (StateFlow, sealed UiState, 람다 안정성)shared/.claude/skills/build.md— 빌드 명령 + configuration-cache 주의
commonMain컴파일 에러:java.io.File,java.util.UUID,System.currentTimeMillis— iOS 에서 안 됨. multiplatform 대안 (kotlinx.io,kotlin.uuid.Uuid,kotlinx.datetime.Clock) 사용 또는expect/actual.- iOS framework 링크 실패 —
:shared:linkDebugFrameworkIosSimulatorArm64직접 실행해서 에러 확인. - iOS 시뮬레이터에서 영상 안 보임 / 메타데이터 unreadable — PHPicker 가 반환하는 absolute path 처리 이슈.
자세한 패턴은
shared/CLAUDE.md의 "Known iOS bug patterns" 참조. - Android
BFF_BASE_URL에 localhost 썼더니 ConnectException — Android 에뮬레이터는 host 의 localhost 가10.0.2.2. 실기기는 LAN IP 필요. ./gradlew실행 권한 없음 —chmod +x ./gradlew.