한국에서 Jellyfin 사용하기

Shovel, Development

친구들끼리 NAS를 새로 구축하고서는 미디어서버로 뭘 쓸까 고민하다가 Open Source 인 Jellyfin을 사용하기로 했다. Jellyfin은 원래 오픈소스였던 Emby의 포크인데, 구글 검색해보니까 Emby에 대해 좋은 평이 나오는 걸 못 보긴 했다. Emby가 까이는 주된 단점은 아래와 같았다.

  1. 자막이 지원되지 않는다. -> 이 글에서 해결할 것이다.
  2. 라이브러리 업데이트가 느리다. -> 이상하게도 파일시스템 감시가 동작하기는 하는데 잘 안되는 것 같긴 하다. 나와 같은 경우 라이브러리 업데이트 간격을 스케쥴러에서 조정해주었다. 수동업데이트가 있으니 이건 그리 치명적인 단점이 아니라 생각한다.

문제는 1번인데, 한글자막을 해결하기 위한 삽질한 내용을 여기에 적어볼까 한다.

발단

원래는 그냥 자막이 안돼서 PLEX나 다른 서버로 갈아탈까 생각을 했다. 하지만, Jellyfin 로그를 봤더니 내부적으로 smi를 브라우저에서 나오는 형식(WebVTT)로 변환하는 과정에서 srt로 변환을 하는데, 그 과정에 오류가 나는 것을 보았다. 근데 뭔가 고칠 수 있을 듯 하여서 손을 대봤다. 오류 로그에서 문제가 된다고 여겨지는 부분만 뽑아보면 다음과 같았다.

Jul 14 20:21:53 ubuntu jellyfin[29088]: [sami @ 0xaaaad79e65f0] UTF16 is automatically converted to UTF8, do not specify a character encoding

Jul 14 20:21:53 ubuntu jellyfin[29088]: [sami @ 0xaaaad7a0f1f0] Unable to recode subtitle event

다음 오류는 오류같아 보이긴 했는데, 무시해도 괜찮은 내용이었다.

Jul 14 20:21:53 ubuntu jellyfin[29088]: Error while decoding stream #0:0: Invalid argument

원인

원인은 FFmpeg에서 잘못된 Charset을 공급받고 있는 것이었다. 에러 로그에 보니까 UTF-16에 대해서는 -sub_charenc 플래그로 자막 Charset을 공급하지 말라고 떠있길래 직접 ffmpeg로 자막 변환을 해보았다. charset 입력을 안하니까 진짜 제대로 변환되더라.

좀 더 자세한 원인으로 들어가보자면, ffmpeg는 ff_text_r8 을 통해 자체적으로 UTF-16 텍스트를 UTF-8로 읽는다. 이 명령어는 ff_text_read 에서 호출이 되고, sami 자막을 디코딩하는 samidec.c 에서 ff_text_read 를 호출한다. 즉, 이미 자체적으로 UTF-16을 번역하는데 또 번역하려고 시도돼서 생기는 오류였다.

해결

직접 문제가 되는 부분을 픽스해서 빌드해보니까 잘 작동이 되었다. 현재 jellyfin#1540 풀 리퀘스트로 해당 수정을 제안해둔 상태인데, 잘 풀린다면 smi 자막을 볼 수 있을 것이라 생각한다.

끝나지 않은 문제

여기까진 참 좋은 이야기였지만, 문제는 또 한 번 더 발생했다. EUC-KR 에서 사용 불가능한 문자가 들어있는 CP949 로 인코딩 된 자막이 들어가면 또 인식이 안되더라. 사실 완성형 2,350 자 정도만 인코딩하면 CP949EUC-KR 와 호환되기에 별 문제가 없다. 문제는 그 범위를 벗어난 문자인데, 해당 문자가 들어있으면 Jellyfin에서 사용하는 CharsetDetector/UTF-Unknown 이  EUC-KR이 아니라고 판단을 하고, charset을 제대로 인식을 못한다. 즉, UTF-Unknown 은 CP949를 인식하지 못한다.

해결2

Puzzlet Chung 님이 파이썬 용 charset detector인 charde 에 커밋하신 내용을 그대로 UTF-Unknown으로 가져왔다. (해당 커밋링크)

현재 UTF-Unknown을 픽스한 내용은 HelloWorld017/UTF-Unknown/tree/cp949 올라와있다.

사실 얘도 Pull Request를 넣어볼 생각이었는데, 윈도우에서 CP949의 인코딩 명이 ks_c_5601-1987 로 괴상망측한 이름을 사용하고 있었다. 그래서 test 를 패스하기 위해 소스에 호환성에 문제될법한 수정을 꽤 가했기 때문에 PR을 넣지 않았다.

jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj 파일에 보면, NuGet에서 <PackageReference> 로 UTF.Unknown 을 가져오는 부분이 있다. 나는 이를 삭제하고 새로 <ProjectReference> 를 추가한 후에, 직접 git submodule 로 UTF.Unknown 을 집어넣어뒀다.

이제 이걸로 잘 해결되었으면 한다.
이 문제를 해결하면서 처음으로 docker를 이용해서 빌드해봤는데, 디펜던시로 골치를 앓을 일이 없어서 참 편안했던 것 같다. 도커 짱짱

(19.08.01 수정)
문제가 하나 생겼다. 자막 중에 '춉' (CP949 0xAD68) 이라는 글자가 들어가니 제대로 파악하지 못했다. 그래서 디버깅을 한 결과 총 두가지 문제가 있었다.

1. Puzzlet Chung님의 코드를 옮기는 과정에서 실수를 한번 범함
2. 원본 코드에서도 제대로 인식되지 않는 걸로 보았을 때, 거기에도 문제가 있어 보인다. 문제를 분석해본 결과, 0xAD을 Class 8로 해두셨는데 Start State (ASCII) 이후에 Class 8 이 나올 경우 Error State 로 가게 만들어두신 듯 했다.

그래서 결국에 State 테이블을 다시 작성해서 현재 HelloWorld017/UTF-Unknown cp949 브랜치에 다시 force-push 해둔 상태이다.

(19.08.14 수정)
정말 놀랍게도, UTF-Unknown에 보낸 Pull Request 가 받아들여졌다!! 이제 jellyfin 측에서 해당 디펜던시를 업데이트한다면 순정 jellyfin에서도 smi 자막이 잘 보일 듯 하다.

결론

오픈소스 좋아요 jellyfin 좋아요
EUC-KR/CP949 싫어요 smi 싫어요