유튜브 다운로더 사용시 자모분리현상 해결 방법

Unraid 서버 환경에서 MeTube를 통해 음원이나 영상을 다운로드하거나, 맥(macOS)에서 작업한 파일을 리눅스 기반 NAS로 이동했을 때 한글 파일명이 'ㄱ ㅏ ㄴ ㅏ ㄷ ㅏ'처럼 낱자 단위로 분리되는 현상을 흔히 겪게 됩니다.

이는 유니코드 정규화 방식 중 NFD(조합형/분리형)와 NFC(완성형/결합형)의 차이 때문에 발생합니다. 특히 음원 파일의 경우 겉으로 보이는 파일명뿐만 아니라 파일 내부의 오디오 태그(ID3, Vorbis Comment)까지 NFD로 기록되어 있으면, Navidrome이나 Jellyfin 같은 미디어 서버에서 곡명과 가수명이 완전히 깨져 검색과 정렬이 불가능해집니다.

본 글에서는 추가적인 리눅스 패키지 설치 없이 파이썬 영구 가상환경(venv)을 활용하여, 여러 경로의 파일명·폴더명과 내부 메타데이터 태그를 단 한 번에 일괄 결합(NFC 변환)하는 완성형 자동화 스크립트를 공유합니다.

1. 스크립트 설계 핵심 원칙

안정적인 Unraid 운영을 위해 스크립트 작성 시 다음 세 가지 원칙을 엄격하게 적용했습니다.

  • Bash 배열 기반 다중 경로 지원: 폴더 경로에 공백(띄어쓰기)이나 특수문자가 포함되어 있어도 오류 없이 처리할 수 있도록 TARGET_DIRS=( ... ) 배열과 "${TARGET_DIRS[@]}" 인자 확장 방식을 채택했습니다.
  • Unraid 휘발성 극복 (영구 venv): Unraid는 램(RAM) 기반 OS이므로 재부팅 시 pip install 내용이 초기화됩니다. 캐시 드라이브의 appdata 경로에 구축된 영구 파이썬 가상환경 바이너리를 직접 호출합니다.
  • Pure Python 기반 파일명 변경: convmv 유틸리티 의존성을 완전히 배제하고 파이썬 내장 unicodedata와 os.rename을 활용했습니다. topdown=False 방식으로 하위 파일부터 상위 폴더 순으로 이름을 변경하므로 경로 유실이 없습니다.
  • 다양한 오디오 포맷 지원: mutagen 라이브러리를 통해 MP3(ID3), FLAC, Opus, OGG(Vorbis), M4A 메타데이터를 통합 처리합니다.

💡 단일 설정 원칙 (Single Source of Truth)

Bash 스크립트 최상단에서 TARGET_DIRS 배열 한 곳만 수정하면, 유효성 검증·파일명 변환·오디오 태그 변환까지 모든 단계에 해당 경로 목록이 인수로 자동 전달되도록 구성하여 이중 수정의 번거로움과 누락을 방지했습니다.

2. 파일명 및 오디오 태그 통합 자동화 스크립트

Unraid의 User Scripts 플러그인에 등록할 완성형 쉘 스크립트입니다. 상단의 TARGET_DIRS 항목에 본인의 라이브러리 경로를 추가하시면 됩니다.

#!/bin/bash
# ==============================================================================
# MeTube / 음악 파일명 및 태그(Vorbis/ID3) 자모 결합 (다중 경로 지원, 영구 venv 기반)
# ==============================================================================

# --- [설정 구간: 처리할 디렉터리 목록] ---
# 원하는 경로를 큰따옴표("")로 감싸서 줄바꿈으로 추가/제거하세요.
TARGET_DIRS=(
    "/mnt/user/sub/ydl"
    "/mnt/user/music"
    "/mnt/user/media/audio"
)

# 파이썬 가상환경 바이너리 경로 지정 (캐시 풀 영구 경로)
VENV_PY="/mnt/user/appdata/scripts-venv/bin/python3"
# ---------------------------------------------

# 1. 파이썬 가상환경 존재 여부 점검
if [ ! -f "$VENV_PY" ]; then
    echo "[오류] 파이썬 가상환경이 존재하지 않습니다. venv 설정을 먼저 확인하세요: $VENV_PY"
    exit 1
fi

# 2. 실제 존재하는 유효한 디렉터리만 필터링
VALID_DIRS=()
for dir in "${TARGET_DIRS[@]}"; do
    if [ -d "$dir" ]; then
        VALID_DIRS+=("$dir")
    else
        echo "[경고] 디렉터리가 존재하지 않아 제외합니다: $dir"
    fi
done

if [ ${#VALID_DIRS[@]} -eq 0 ]; then
    echo "[오류] 처리할 수 있는 유효한 디렉터리가 없습니다. 경로 설정을 확인하세요."
    exit 1
fi

# 3. [1단계] 파일명 및 폴더명 NFC 자모 결합 (Pure Python)
echo "--- [1/2] 파일명 및 폴더명 NFC 변환 실행 (총 ${#VALID_DIRS[@]}개 디렉터리) ---"
"$VENV_PY" - "${VALID_DIRS[@]}" << 'EOF'
import sys
import os
import unicodedata

target_dirs = sys.argv[1:]
total_renamed = 0

for target_dir in target_dirs:
    print(f"\n>> 탐색 디렉터리: {target_dir}")
    dir_renamed = 0

    # 하위 경로가 먼저 변경되도록 topdown=False 적용
    for root, dirs, files in os.walk(target_dir, topdown=False):
        for name in files:
            nfc_name = unicodedata.normalize('NFC', name)
            if name != nfc_name:
                old_path = os.path.join(root, name)
                new_path = os.path.join(root, nfc_name)
                try:
                    if not os.path.exists(new_path):
                        os.rename(old_path, new_path)
                        dir_renamed += 1
                        print(f" [파일명 변환] {name} -> {nfc_name}")
                except Exception as e:
                    print(f" [파일명 변환 실패] {name}: {e}")

        for name in dirs:
            nfc_name = unicodedata.normalize('NFC', name)
            if name != nfc_name:
                old_path = os.path.join(root, name)
                new_path = os.path.join(root, nfc_name)
                try:
                    if not os.path.exists(new_path):
                        os.rename(old_path, new_path)
                        dir_renamed += 1
                        print(f" [폴더명 변환] {name} -> {nfc_name}")
                except Exception as e:
                    print(f" [폴더명 변환 실패] {name}: {e}")

    print(f"   -> {target_dir} 이름 정규화 완료: {dir_renamed}개 변경됨")
    total_renamed += dir_renamed

print(f"\n[파일명 작업 완료] 총 {total_renamed}개 파일 및 폴더 이름 정규화 완료.\n")
EOF

# 4. [2단계] 내부 메타데이터 태그 NFC 일괄 정규화
echo "--- [2/2] 오디오 태그(Vorbis/ID3) NFC 변환 실행 ---"
"$VENV_PY" - "${VALID_DIRS[@]}" << 'EOF'
import sys
import os
import unicodedata
import mutagen

target_dirs = sys.argv[1:]
EXTENSIONS = ('.opus', '.ogg', '.flac', '.mp3', '.m4a')
total_modified = 0

for target_dir in target_dirs:
    print(f"\n>> 탐색 디렉터리: {target_dir}")
    dir_modified = 0

    for root, _, files in os.walk(target_dir):
        for filename in files:
            if filename.lower().endswith(EXTENSIONS):
                file_path = os.path.join(root, filename)
                try:
                    audio = mutagen.File(file_path)
                    if audio is None or audio.tags is None:
                        continue

                    is_changed = False
                    for key in list(audio.tags.keys()):
                        val = audio.tags[key]

                        # 1) ID3 (MP3) 프레임
                        if hasattr(val, 'text') and isinstance(val.text, list):
                            new_text = [unicodedata.normalize('NFC', t) if isinstance(t, str) else t for t in val.text]
                            if new_text != val.text:
                                val.text = new_text
                                is_changed = True

                        # 2) Vorbis Comment (Opus, OGG, FLAC) 리스트
                        elif isinstance(val, list):
                            new_list = [unicodedata.normalize('NFC', item) if isinstance(item, str) else item for item in val]
                            if new_list != val:
                                audio.tags[key] = new_list
                                is_changed = True

                        # 3) 단순 문자열 태그
                        elif isinstance(val, str):
                            normalized = unicodedata.normalize('NFC', val)
                            if normalized != val:
                                audio.tags[key] = normalized
                                is_changed = True

                    if is_changed:
                        audio.save()
                        dir_modified += 1
                        print(f" [태그 변환 완료] {filename}")

                except Exception as err:
                    print(f" [처리 실패] {filename}: {err}")

    print(f"   -> {target_dir} 폴더 태그 작업 완료: {dir_modified}개 파일 수정됨")
    total_modified += dir_modified

print(f"\n[전체 작업 완료] 총 {total_modified}개 파일 태그 정규화 완료.")
EOF

3. Unraid 실전 적용 및 자동화 가이드

작성된 스크립트를 Unraid 시스템에 안정적으로 정착시키는 3단계 절차입니다.

Step 1. 영구 파이썬 venv 환경 구성 확인

Unraid 터미널에서 캐시 드라이브 영구 경로에 가상환경이 생성되어 있고, 필수 라이브러리인 mutagen이 설치되어 있는지 확인합니다. 아직 생성 전이라면 아래 명령어로 최초 1회 생성해 줍니다.

# 가상환경 생성 및 mutagen 라이브러리 설치
python3 -m venv /mnt/user/appdata/scripts-venv
/mnt/user/appdata/scripts-venv/bin/pip install mutagen

Step 2. User Scripts 등록 및 스케줄링 설정

  1. Unraid 웹 GUI에서 Settings > User Scripts로 이동합니다.
  2. [Add New Script] 버튼을 누르고 스크립트 이름(예: normalize_korean_media)을 지정합니다.
  3. 생성된 스크립트 옆의 기어 아이콘을 누르고 Edit Script를 선택한 뒤, 위 코드를 복사하여 붙여넣고 저장합니다.
  4. 스케줄 드롭다운에서 주기(예: Custom: */30 * * * * 또는 Hourly)를 설정하여 자동 실행되도록 등록합니다.

👉 Unraid 공식 User Scripts 플러그인 안내 바로가기

Step 3. 수동 실행 테스트 및 로그 검증

설정 완료 후 즉시 [Run Script] 버튼을 눌러 백그라운드 로그를 확인합니다. 등록한 폴더 개수만큼 순차적으로 탐색 디렉터리가 표시되고, 자모 분리 현상이 있던 파일명과 오디오 태그가 결합된 후 [전체 작업 완료] 문구가 출력되면 정상 적용된 것입니다.

💬 댓글 0