안녕하세요 오늘은 음성 데이터셋을 정리할 때 자주 사용하는 JSONL 포맷에 대해 이야기해보려고 합니다.
AI 프로젝트를 하다 보면 오디오 파일과 그에 대응하는 전사(텍스트)를 한꺼번에 관리해야 하는데,
이때 단순 CSV보다 확장성과 유연성이 좋은 형식이 바로 JSONL(JSON Lines) 입니다.
특히 최근 많이 쓰이는 허깅페이스(HuggingFace) 라이브러리에서는 JSONL을 사실상 표준처럼 지원하기 때문에,
음성 데이터셋을 학습용으로 준비할 때 JSONL 포맷으로 변환해두면 바로 불러와서 사용할 수 있습니다.
오늘 글에서는
- JSONL이 무엇인지,
- 왜 음성 데이터셋에서 JSONL을 쓰는지,
- 그리고 Python 코드로 직접 변환하는 방법
까지 함께 살펴보겠습니다.
JSONL 이란?
- JSON Lines: 한 줄에 하나의 JSON 객체
- 대용량 데이터에 적합, 스트리밍/병합 쉬움
- HuggingFace datasets의 json 로더가 그대로 읽음
허깅페이스 (HuggingFace) 란?
허깅페이스(HuggingFace)는 전 세계적으로 가장 널리 쓰이는 머신러닝/딥러닝 오픈소스 플랫폼 중 하나입니다.
특히 자연어 처리(NLP)와 음성·이미지 분야에서 강력한 생태계를 가지고 있고, 연구자와 개발자들이 만든 수많은 사전학습 모델(Pretrained Models)과 데이터셋이 공유되는 허브 역할을 합니다.
Hugging Face – The AI community building the future.
The Home of Machine Learning Create, discover and collaborate on ML better. We provide paid Compute and Enterprise solutions. We are building the foundation of ML tooling with the community.
huggingface.co
왜 음성 데이터셋에서 JSONL 을 사용할까 ?
음성 데이터셋은 단순히 파일 경로, 텍스트 전사 데이터만 있는 것이 아니라 경우에 따라 스피커 정보, 감정 레이블, 언어, 샘플링 레이트, 발화 길이 등 다양한 부가 정보를 함께 저장해야하는 경우가 많습니다.
이때 CSV 나 txt 같은 단순한 포맷은 새로운 정보를 추가할수록 컬럼이 늘어나 관리가 복잡해집니다.
반면, JSONL 은 한줄에 하나의 샘플을 json 객체로 표현하기 때문에 필요할때마다 필드를 자유롭게 추가할 수 있고 구조적으로도 깔끔합니다.
또한 대규모 처리에 적합하며, 허깅 페이스 라이브러리와의 호환성이 좋습니다.
따라서, 특히 허깅페이스 기반 프로젝트일 경우 JSONL 로 관리하는 것이 가장 편리하다고 할 수 있습니다.
TTS 학습 시에는 대부분 list.txt, metadata.csv 형식만으로 충분하기 때문에 JSONL 형식이 필요 없지만
데이터셋을 공유 or 배포하거나 추후 재사용, 관리를 위해 사용되는 포맷입니다.
1. JSONL 생성 스크립트
시나리오: 텍스트에 알맞는 이모지가 매핑되는 데이터셋입니다.
import csv, json, hashlib
from pathlib import Path
import soundfile as sf
# ===== 경로 설정 =====
AUDIO_DIR = Path(r"C:\Users\bhone\Downloads\voice\trimed_data") # 필요시 trimmed_data로 정리
TRANS_CSV = Path(r"C:\Users\bhone\Downloads\voice\transcripts.csv") # filename,text
OUT_JSONL = Path("train.jsonl")
# ===== 옵션 =====
LANGUAGE = "ko"
ALLOW_MISSING_TEXT = False # 전사 없으면 건너뛰기(False) / 빈 문자열 허용(True)
# ----- 전사 매핑 불러오기 -----
text_map = {}
if TRANS_CSV.exists():
with open(TRANS_CSV, "r", encoding="utf-8") as f:
rdr = csv.DictReader(f)
for row in rdr:
fname = row["filename"].strip()
text = row["text"].strip()
text_map[fname] = text
# ----- 유틸 -----
def md5sum(path: Path, chunk=1024 * 1024):
h = hashlib.md5()
with open(path, "rb") as f:
while True:
b = f.read(chunk)
if not b:
break
h.update(b)
return h.hexdigest()
def parse_meta_from_name(stem: str):
# 예: emo_surprise_e_001 -> ["emo", "surprise", "e", "001"]
parts = stem.split("_")
emotion = parts[1] if len(parts) > 1 else ""
speaker = parts[2] if len(parts) > 2 else ""
emoji_map = {"surprise":"🫨", "happy":"😆", "angry":"😡", "sad":"😢"}
emoji = emoji_map.get(emotion, "")
return emotion, speaker, emoji
def safe_relpath(p: Path, base: Path) -> str:
try:
return p.relative_to(base).as_posix()
except Exception:
return p.as_posix()
# ----- 생성 -----
count, skipped = 0, 0
with open(OUT_JSONL, "w", encoding="utf-8") as out:
for wav in sorted(AUDIO_DIR.glob("*.wav")):
try:
info = sf.info(wav) # 로딩 없이 메타만
duration = float(info.frames) / float(info.samplerate)
num_samples = int(info.frames)
sr = int(info.samplerate)
fname = wav.name
stem = wav.stem
emotion, speaker, emoji = parse_meta_from_name(stem)
# 전사 찾기
text = text_map.get(fname, "")
if not text and not ALLOW_MISSING_TEXT:
skipped += 1
continue
item = {
# 부모 폴더 기준 상대경로(안되면 절대경로)
"audio": safe_relpath(wav, AUDIO_DIR.parent),
"text": text,
"speaker": speaker,
"emotion": emotion,
"emoji": emoji,
"language": LANGUAGE,
"sampling_rate": sr,
"duration": round(duration, 6),
"num_samples": num_samples,
"md5": md5sum(wav),
}
out.write(json.dumps(item, ensure_ascii=False) + "\n")
count += 1
except Exception as e:
print(f"⚠️ 실패: {wav.name} ({e})")
skipped += 1
print(f"✅ JSONL 생성 완료: {OUT_JSONL} | ok={count}, skipped={skipped}")
'Python' 카테고리의 다른 글
| [Python] 음성 데이터 품질 검사(QC) 자동화 리포트 만들기 (1) | 2025.08.20 |
|---|---|
| [Python] Pytorch에서 벡터 합치기 (임베딩 결합 방식) (0) | 2025.08.19 |
| [Python] librosa로 WAV 파일 무음 제거하기 (1) | 2025.08.17 |
| [Python] PyTorch 활용해서 손글씨 데이터를 숫자로 분류하기 (2) | 2025.08.13 |
| [Python] 오디오 데이터 전처리 하기 (1) | 2025.08.12 |