왜 만들었나
기기 기본 TTS는 '3개', '요 3:16', 'AI' 같은 한국어 숫자·약어를 어색하게 읽고, 짧은 문장마다 억양이 끊깁니다. 인터넷 없이 자연스러운 음성으로 책을 듣고, 틀린 발음은 직접 고칠 수 있게 했습니다.
주요 기능
- 기기 안의 EPUB을 복사 없이 찾아 서재·책장으로 정리, DRM 걸린 책은 열지 않고 알림
- Supertonic 음성 팩(sherpa-onnx)으로 기기 내 합성, 다음 문장 미리 합성으로 끊김 없이 낭독
- 따옴표로 시작하는 대사만 다른 목소리로 읽기
- 숫자·날짜·단위·성경 구절·한자 병기를 읽는 말로 바꾸는 한국어 정규화와 책별/전체 발음 사전
- 애니메이션 없는 흑백 UI, 켬|끔 칸 버튼과 −/+ 버튼 등 e-ink에 맞춘 화면
숫자로 보면
고유어로 읽는 단위어 28개(개·명·살·마리 등)
성경 66권 이름·약어를 인식해 '요 3:16' → '요한복음 삼장 십육절'로 변환
JUnit 단위 테스트 29개
어떻게 동작하나
책 찾기
저장소를 walkTopDown으로 훑어 숨김·Android 폴더를 건너뛰고 .epub만 canonicalPath 기준으로 중복 없이 모읍니다.
app/src/main/java/app/voicereader/DeviceScan.kt열기·DRM 검사
Readium으로 책을 열고 isRestricted와 META-INF/encryption.xml을 확인해 폰트 난독화 외 암호화가 있으면 거절합니다.
app/src/main/java/app/voicereader/App.kt문장 나누기
Readium 토크나이저로 문장을 자른 뒤 문장 첫머리 대사를 따로 떼고, 40자 미만 문장은 120자 이내로 이어 붙입니다.
korean/src/main/kotlin/app/voicereader/korean/Dialogue.kt읽을 글로 변환
TTS 엔진을 감싼 래퍼가 문장마다 발음 사전과 KoreanNormalizer를 적용하고, 영어로만 된 문장은 영어로 읽게 합니다.
app/src/main/java/app/voicereader/TransformingEngine.kt미리 합성
전용 스레드가 대기열의 다음 문장을 미리 합성해 캐시에 두고, 재생할 문장이 준비 안 됐으면 대기열 맨 앞으로 올립니다.
app/src/main/java/app/voicereader/SupertonicEngine.kt재생
앞뒤 무음을 잘라낸 오디오를 재생하며 Media3 세션으로 백그라운드·잠금화면·이어폰 버튼을 지원합니다.
app/src/main/java/app/voicereader/PlaybackService.kt
핵심 코드
private fun readNumber(m: MatchResult): String {
val (prefix, grouped, fraction, unit, counter) = m.destructured
val raw = grouped.replace(",", "")
val n = raw.toLongOrNull()
val number = when {
n == null -> digits(raw, zero = "영")
raw.length > 1 && raw.startsWith("0") -> digits(raw, zero = "공")
counter == "번째" && n == 1L && prefix.isEmpty() -> "첫"
counter == "월" && n == 6L -> "유"
counter == "월" && n == 10L -> "시"
counter in NATIVE_COUNTERS && prefix.isEmpty() && fraction.isEmpty() -> native(n)
else -> sino(n)
}
val point = if (fraction.isEmpty()) "" else " 점 " + digits(fraction.drop(1), zero = "영")
// …숫자 뒤에 오는 단위어에 따라 고유어(세 개)·한자어(오 개월)·특수 읽기(유월, 첫)를 고르는 부분입니다.
어려웠던 점
Supertonic이 문장마다 억양과 음높이를 새로 잡아 짧은 문장이 이어지면 다른 사람이 번갈아 읽는 것처럼 들렸습니다. 짧은 문장을 묶되 대사와 서술은 서로 섞이지 않게 나누고, 앞뒤 약 250ms 무음을 잘라 문장 사이 공백을 줄였습니다.



