본문 바로가기
C.W.K.
Stream
Lesson 01 of 05 · published

배치와 실시간

~13 min · speech-to-text, scribe, batch, realtime, language

Level 0음소거
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"귀에는 속도가 두 개 있어. 상황에 맞는 쪽을 쓰고, 어느 쪽에도 언어를 짐작하게 두지 마."

가족 전체에 전사 업체는 하나

말을 글로 바꾸는 cwk 앱은 전부 전사 모델 한 계열을 써. ElevenLabs Scribe고, 계정은 가족 음성 엔진 Bellows가 쥔 그 하나야. 대부분은 Bellows를 거쳐 들어가고, 영상 보관소의 전사기는 Bellows의 키 파일을 읽어서 Scribe를 직접 불러. 어느 쪽이든 영상 보관소의 전사문, 폰의 마이크, 텔레그램 음성 메모, 음성 모드가 전부 같은 모델 버전을 타. 업체가 하나면 익힐 버릇도 한 벌, 고칠 곳도 한 군데, 지킬 키도 하나야.

배치 길

배치 길은 음성 모드보다 먼저 있던 길이야. 말 한 덩어리를 통째로 녹음해서 파일을 올리면 전사문이 돌아와. cwkPippa는 이걸 자기 /api/stt 라우트로 열어두고 호출을 Bellows로 넘기고, Bellows가 Scribe v2를 불러. 단순하고 빠짐없고, 덩치에 비해 빨라. 2026-09-25에 한국어 5초짜리와 10초짜리를 보냈더니 둘 다 0.64초쯤에 돌아왔어. 약점은 속도가 아니라 모양이야. 녹음이 끝나야 뭔가 시작되니까, 그게 언제인지 누군가 정해야 해. 음성 모드 첫 판에선 그 누군가가 완료 버튼을 누르는 아빠였어.

실시간 길

핸즈프리로 들으려면 다른 속도가 필요해. Scribe v2 Realtime은 웹소켓이야. 클라이언트는 16 kHz PCM을 잘게 잘라 input_audio_chunk 메시지로 흘려보내고, 글은 두 종류로 돌아와. partial_transcript는 지금까지 들은 걸로 모델이 추측한 문장이고, 소리가 더 들어오면 고쳐져. 음성 화면은 이걸 실시간으로 보여줘서 아빠가 자기 말이 들리고 있다는 걸 알게 해. committed_transcript는 모델이 더는 안 고칠 확정 조각이야. 음성 구간 감지 전략을 쓰면 모델이 조용한 틈이 좀 이어졌을 때 알아서 확정해 주고, 루프는 여기서 문장 하나가 끝났다는 첫 신호를 받아.

두 길은 경쟁자가 아니야. 대화는 실시간 길이 굴려. 배치 길은 여전히 입력창 마이크, 음성 메모, 워치 녹음을 받고, 트랙 2의 네 번째 레슨에서 보듯 실시간 모델이 단어를 흘렸을 만큼 긴 말은 한 번 더 통째로 받아써.

언어는 고르는 거지, 짐작하는 게 아니야

두 길 다 언어 코드를 받고, 빼먹으면 알아서 감지도 해. 빼먹지 마. 아빠는 한국어 문장 한가운데 영어 용어를 섞어 말하고, 2026-09-11에 그런 말투엔 자동 감지를 못 쓴다고 정했어. 섞인 소리 몇 초로 판정하는 감지기는 추측이 제일 어려운 바로 그 지점에서 추측하는 셈이거든. 그래서 가족 앱 화면엔 전부 마이크가 두 개야. KO와 EN. 아빠가 누른 쪽이 language_code로 Scribe까지 가. 음성 세션은 한국어로 시작하고 칩 하나로 바꿔. 제공자의 자동 감지는 옛 호출자를 위해 코드에만 남아 있고, 고를 수 있는 모드로는 절대 안 내놔.

Code

배치 전사, 그리고 실시간 소켓이 돌려주는 것·python
import os

import httpx

STT_URL = "https://api.elevenlabs.io/v1/speech-to-text"


def transcribe(path: str, language: str) -> str:
    """The batch path: one whole recording in, one transcript out.

    `language` is the mic Dad pressed ("ko" or "en"). Never omit it.
    """
    if language not in {"ko", "en"}:
        raise ValueError("pick KO or EN; auto-detect is not offered")
    with open(path, "rb") as audio:
        response = httpx.post(
            STT_URL,
            headers={"xi-api-key": os.environ["ELEVENLABS_API_KEY"]},
            data={"model_id": "scribe_v2", "language_code": language},
            files={"file": (os.path.basename(path), audio, "audio/wav")},
            timeout=60,
        )
    response.raise_for_status()
    return response.json()["text"].strip()


def on_realtime_message(message: dict, segments: list[str]) -> str | None:
    """The realtime path, one server message at a time.

    Returns the live caption to show, or None when nothing changed on screen.
    """
    kind = message.get("message_type")
    words = (message.get("text") or "").strip()
    if kind == "partial_transcript":
        return " ".join([*segments, words]).strip()   # a guess, revised later
    if kind == "committed_transcript" and words:
        segments.append(words)                         # final for this segment
        return " ".join(segments)
    if kind in {"session_started", "committed_transcript_with_timestamps", "warning"}:
        return None
    if message.get("error"):
        raise RuntimeError(f"Scribe refused: {kind}: {message['error']}")
    return None


if __name__ == "__main__":
    segments: list[str] = []
    for msg in (
        {"message_type": "partial_transcript", "text": "내일 일산"},
        {"message_type": "partial_transcript", "text": "내일 일산 날씨"},
        {"message_type": "committed_transcript", "text": "내일 일산 날씨 어때?"},
    ):
        print(on_realtime_message(msg, segments))

External links

Exercise

같은 20초짜리 문장을 두 번 녹음해. 한 번은 한 언어로만, 한 번은 영어 기술 용어 다섯 개를 섞어서. 각각 언어를 명시해서 한 번, 빼고 한 번 받아써. 전사문 네 개를 단어 단위로 비교하고, 자동 감지가 어디서 틀렸는지 적어.
Hint
뭐가 나오든, 다시 읽어 보지 않고도 바로 쓸 수 있었던 버전이 어느 쪽인지 적어. 언어를 못 박은 쪽만 쓸 만한 전사문을 낸 경우가 한 번이라도 있으면, 그 한 번이 마이크 두 개의 근거야. 음성 루프는 다시 읽어 달라고 부탁할 수가 없거든.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.