젤리핀(jellyfin) 한글 정렬 문제 해결법

Unraid나 Docker 환경에서 Jellyfin 미디어 서버와 함께 미디어 자동화 스택인 *arr 시리즈(Radarr, Sonarr, Seerr 등)를 구축해 운용하다 보면 유독 골치 아픈 현상을 마주하게 됩니다. 포스터 아래에는 '미키 17', '그녀'처럼 고화질 한글 제목이 정상 표시되는데, 정렬 기준을 '제목'으로 두면 'ㄱ-ㅎ' 순서가 아닌 영문 알파벳 구역(M, H)으로 튕겨 나가는 문제입니다.

심지어 한국 영화인 '수상한 그녀'조차 해외 공식 배급명인 Miss Granny로 인식되어 'M' 구역에 배치되곤 합니다. 더 심각한 문제는 데이터베이스(DB)나 웹 UI에서 정렬 제목을 수동으로 수정해 두어도, '라이브러리 검사(Scan Media Library)'를 한 번만 실행하면 귀신같이 원래의 영문 정렬 순서로 롤백된다는 점입니다.

본 포스팅에서는 단순한 임시방편이 아닌, *arr 스택의 NFO 파일 유입 메커니즘을 규명 및 차단하고, Jellyfin 10.9+ 최신 스키마 환경에서 SQLite 자동 동기화 트리거(Trigger)를 구축하여 한글 정렬을 영구적으로 방어하는 완전 통합 솔루션을 정리합니다.

1. 정렬 오류와 무한 롤백의 구조적 원인 (Root Cause)

이 문제는 Jellyfin 단독의 버그가 아니라, 미디어가 다운로드되어 라이브러리에 적재되는 파이프라인 전반의 구조적 동작 방식이 얽혀 발생합니다.

  • Jellyfin의 SortName 잠금(Field Lock) 부재: Jellyfin 백엔드 메타데이터에는 제목(Name), 줄거리(Overview) 등을 보호하는 잠금 열거형은 존재하지만, 정렬 제목(SortName)은 잠금 항목 자체가 없습니다. 라이브러리 스캔 시 TMDb 스크래퍼가 빈 정렬 필드에 공식 영문명을 강제로 재주입합니다.
  • *arr 스택의 .nfo 유입과 디스크 간섭: 릴리즈 그룹이 배포한 원본 압축 해제 폴더의 영문 .nfo 파일이 Radarr/Sonarr의 Import Extra Files 설정에 의해 미디어 폴더로 자동 복사됩니다. Jellyfin은 로컬 NFO를 '단일 진실 공급원'으로 취급하므로, 디스크에 NFO가 존재하는 한 DB를 아무리 고쳐도 원제로 영구 덮어쓰기됩니다.
  • Jellyfin 10.9 DB 스키마 개편: 10.9 버전(EF Core 도입)부터 과거 커뮤니티 스크립트에서 사용하던 레거시 뷰(TypedBaseItems)가 완전히 삭제되고 BaseItems 단일 원본 테이블로 통합되었습니다.
  • DB 중첩 경로 및 인메모리 캐시: 컨테이너 내부의 --datadir /config/data 구조로 인해 실제 DB는 /config/data/data/jellyfin.db에 존재합니다. 또한 정렬 인덱스가 RAM에 캐싱되므로 컨테이너 재시작과 브라우저 강력 새로고침이 필수적입니다.

💡 Seerr(Jellyseerr/Overseerr)는 이 문제와 관련이 있을까요?

전혀 무관합니다. Seerr 계열은 사용자의 요청을 접수하여 Radarr/Sonarr API로 전달하는 순수 웹 UI/API 중계 프록시입니다. 실제 미디어 저장 디렉터리 볼륨을 마운트하지 않으며 파일 쓰기 작업을 일절 수행하지 않으므로, NFO 생성이나 메타데이터 오염의 원인에서 완전히 배제하셔도 됩니다.

2. Step 1. *arr 스택 및 Jellyfin의 NFO 파이프라인 완전 차단

새로운 미디어가 들어올 때 영문 NFO가 다시 생성되거나 복사되지 않도록 업스트림 설정을 먼저 정리해야 합니다.

1. Radarr / Sonarr NFO 유입 차단 설정

  1. 자체 메타데이터 작성 끄기: Settings > Metadata 메뉴에서 등록된 메타데이터 프로파일(Kodi, Emby 등)이 있다면 모두 Disabled(비활성화)로 변경합니다.
  2. 다운로드 파일 복사 제외: Settings > Media Management로 이동하여 우측 상단의 Show Advanced를 활성화합니다.
  3. File Management 섹션의 Import Extra Files 입력창에서 nfo 확장자를 제거하거나 해당 항목을 비웁니다.

2. Jellyfin 라이브러리 NFO 비활성화

Jellyfin 웹 UI의 관리 대시보드 > 라이브러리에서 관리 중인 각 라이브러리(영화, TV 등)의 라이브러리 관리로 진입합니다. 메타데이터 저장기(Metadata savers)와 메타데이터 판독기(Metadata readers) 목록에서 Nfo 체크박스를 모두 해제하고 저장합니다.

3. 디스크 내 잔여 .nfo 파일 전량 일괄 영구 삭제

현대 미디어 서버 환경에서 NFO 파일은 불필요한 I/O와 메타데이터 꼬임만 유발하는 레거시 잔재입니다. Unraid 호스트 터미널에서 미디어 폴더 내에 남아있는 NFO 파일을 안전하게 확인 후 일괄 삭제합니다.

# 1. 잔여 .nfo 파일 개수 사전 확인 (경로는 본인의 실제 미디어 경로로 수정)
find /mnt/user/data/media -type f -name "*.nfo" | wc -l

# 2. 미디어 하위 경로의 모든 .nfo 파일 전량 일괄 영구 삭제
find /mnt/user/data/media -type f -name "*.nfo" -delete

# 3. 삭제 완료 검증 (출력값이 0이어야 정상)
find /mnt/user/data/media -type f -name "*.nfo" | wc -l

✅ 출력 검증 결과 (Expected Output):

0

3. Step 2. 실제 운용 DB 타깃팅 및 더미 파일 정리

Jellyfin 컨테이너 내부 콘솔(docker exec -it jellyfin /bin/sh)에 진입하여 정확한 데이터베이스 파일을 타깃팅합니다.

# 1. 실제 운용 중인 하위 DB 파일 용량 검증 (수십 MB ~ 수백 MB 단위)
ls -lh /config/data/data/jellyfin.db

# 2. 이전에 상위 경로에 잘못 생성되었던 0바이트 더미 파일 정리
rm -f /config/data/jellyfin.db

✅ 출력 검증 결과 (Expected Output):

-rw-r--r-- 1 abc abc 45.2M Oct 17 21:00 /config/data/data/jellyfin.db

4. Step 3. SQLite 실시간 자동 동기화 트리거 구축 (영구 해결책)

Jellyfin 스캐너나 TMDb가 정렬 제목(SortName)을 영문으로 덮어쓰려 하거나 새 미디어가 등록될 때, DB 엔진 레벨에서 이를 실시간으로 가로채어 표시 제목(Name, 한글명)으로 즉각 강제 복구하는 트리거 2종을 등록합니다. (영화, 시리즈, 컬렉션 완벽 대응)

# 1. UPDATE 트리거: SortName 영문 변경 시 한글 제목(Name)으로 강제 롤백
sqlite3 /config/data/data/jellyfin.db "CREATE TRIGGER IF NOT EXISTS trg_sync_sortname_update AFTER UPDATE OF SortName ON BaseItems FOR EACH ROW WHEN NEW.SortName != NEW.Name AND (NEW.type LIKE '%Movie%' OR NEW.type LIKE '%Series%' OR NEW.type LIKE '%BoxSet%') BEGIN UPDATE BaseItems SET SortName = NEW.Name WHERE Id = NEW.Id; END;"

# 2. INSERT 트리거: 신규 미디어 추가 시 SortName을 Name과 강제 일치
sqlite3 /config/data/data/jellyfin.db "CREATE TRIGGER IF NOT EXISTS trg_sync_sortname_insert AFTER INSERT ON BaseItems FOR EACH ROW WHEN NEW.SortName != NEW.Name AND (NEW.type LIKE '%Movie%' OR NEW.type LIKE '%Series%' OR NEW.type LIKE '%BoxSet%') BEGIN UPDATE BaseItems SET SortName = NEW.Name WHERE Id = NEW.Id; END;"

트리거가 정상 등록되었는지 확인합니다.

sqlite3 /config/data/data/jellyfin.db "SELECT name, tbl_name FROM sqlite_master WHERE type = 'trigger';"

✅ 출력 검증 결과 (Expected Output):

trg_sync_sortname_update|BaseItems
trg_sync_sortname_insert|BaseItems

5. Step 4. 기존 미디어 정렬 제목 전량 일괄 동기화 (Backfill)

트리거는 등록 시점 이후에 발생하는 이벤트에만 작동하므로, 과거에 이미 영문으로 고착화된 기존 레코드들을 1회성으로 모두 한글 제목과 강제 일치시킵니다.

# 기존 레코드의 SortName을 Name으로 일괄 갱신
sqlite3 /config/data/data/jellyfin.db "UPDATE BaseItems SET SortName = Name WHERE (type LIKE '%Movie%' OR type LIKE '%Series%' OR type LIKE '%BoxSet%') AND SortName != Name;"

6. Step 5. 실시간 방어 검증 및 캐시 갱신

구축된 트리거가 영문 주입을 실제로 차단하는지 검증하고 시스템 캐시를 갱신합니다.

1. 영문 임의 주입 방어 테스트

테스트를 위해 영화 '그녀'의 정렬 제목을 영문 소문자 'her'로 강제 업데이트해 봅니다.

sqlite3 /config/data/data/jellyfin.db "UPDATE BaseItems SET SortName = 'her' WHERE Name = '그녀' AND type LIKE '%Movie%'; SELECT Name, SortName FROM BaseItems WHERE Name = '그녀' AND type LIKE '%Movie%';"

✅ 출력 검증 결과 (Expected Output):

그녀|그녀

출력값이 그녀|her가 아니라 그녀|그녀로 반환된다면, 영문 업데이트 트랜잭션이 발생하자마자 트리거가 0.001초 만에 표시 이름으로 자동 복구한 것입니다.

2. 컨테이너 재시작 및 웹 캐시 초기화

Jellyfin 프로세스가 메모리에 적재해 둔 구형 정렬 인덱스를 비우고 웹 클라이언트 캐시를 갱신합니다.

  1. 컨테이너 콘솔을 빠져나온 후 Jellyfin 컨테이너를 재시작합니다.
    exit
    docker restart jellyfin
  2. Jellyfin 웹 UI에 접속하여 관리 대시보드 > 예약된 작업에서 [라이브러리 검사(Scan Media Library)]를 1회 실행합니다.
  3. 웹 브라우저에서 Ctrl + Shift + R(또는 Ctrl + F5)을 눌러 브라우저 캐시를 완전히 비웁니다.

이제 전체 라이브러리 검사가 완료되어도 '미키 17'은 'ㅁ' 구역에, '수상한 그녀'는 'ㅅ' 구역에 완벽하게 고정되며, Radarr/Sonarr를 통해 새로운 미디어가 주기적으로 추가되더라도 영구적으로 깨짐 없는 가나다순 한글 정렬을 유지할 수 있습니다.

👉 Jellyfin 공식 라이브러리 메타데이터 문서 👉 Radarr 공식 미디어 매니지먼트 가이드

💬 댓글 0