본문 바로가기
프로그램

[파이썬] 여러 곡이 있는 mp3 파일 분리하기

by 오디세이99 2025. 6. 4.
728x90
반응형

 

aa,mp3 라는 파일에 여러 음악(노래)가 있는 경우, 각각의 노래로 분리

aa.txt 파일에 다음과 같이 '/'로 분리되어 있어야 합니다.

시작시간 / 노래제목(파일명) / 음악가 정보

00:00:00/Song name/음악가
00:04:14/Song name/음악가

 

그럼 txt 파일의 정보로 mp3 파일을 분리합니다.

 

from pydub import AudioSegment
from mutagen.id3 import ID3, TIT2, TPE1, TPE2 # ID3 태그를 위한 클래스 임포트
from mutagen.mp3 import MP3
import os

def split_mp3_by_txt(mp3_filepath, txt_filepath, output_directory="output_splits"):
    """
    텍스트 파일에 지정된 시작 시간, 제목, 음악가 정보에 따라 MP3 파일을 분리하고,
    각 파일에 제목과 참여 음악가 ID3 태그를 추가합니다.
    각 곡의 종료 시간은 다음 곡의 시작 시간 직전으로 자동 계산됩니다.
    마지막 곡의 종료 시간은 원본 MP3 파일의 총 길이입니다.

    Args:
        mp3_filepath (str): 입력 MP3 파일의 경로.
        txt_filepath (str): 분리 정보가 포함된 텍스트 파일의 경로 (형식: 시작시간/제목/음악가).
        output_directory (str): 분리된 MP3 파일이 저장될 디렉토리.
                                 기본값은 "output_splits"입니다.
    """
    if not os.path.exists(mp3_filepath):
        print(f"오류: MP3 파일 '{mp3_filepath}'을(를) 찾을 수 없습니다.")
        return

    if not os.path.exists(txt_filepath):
        print(f"오류: 텍스트 파일 '{txt_filepath}'을(를) 찾을 수 없습니다.")
        return

    os.makedirs(output_directory, exist_ok=True)

    try:
        audio = AudioSegment.from_mp3(mp3_filepath)
        total_audio_length_ms = len(audio) # MP3 파일의 총 길이 (밀리초)
    except Exception as e:
        print(f"MP3 파일 로드 중 오류 발생: {e}")
        return

    # 시간을 밀리초로 변환하는 헬퍼 함수
    def time_to_milliseconds(time_str):
        time_parts = list(map(int, time_str.split(':')))
        if len(time_parts) == 2:  # MM:SS
            minutes, seconds = time_parts
            return (minutes * 60 + seconds) * 1000
        elif len(time_parts) == 3:  # HH:MM:SS
            hours, minutes, seconds = time_parts
            return (hours * 3600 + minutes * 60 + seconds) * 1000
        else:
            raise ValueError("지원되지 않는 시간 형식입니다. MM:SS 또는 HH:MM:SS를 사용하세요.")

    # 밀리초를 HH:MM:SS 또는 MM:SS 형식으로 변환 (로그 출력용)
    def ms_to_hms(ms):
        seconds = ms // 1000
        minutes = seconds // 60
        hours = minutes // 60
        seconds %= 60
        minutes %= 60
        if hours > 0:
            return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
        else:
            return f"{minutes:02d}:{seconds:02d}"

    segments_info = [] # (start_ms, title, artist) 저장
    with open(txt_filepath, 'r', encoding='utf-8') as f:
        lines = f.readlines()

    print(f"'{os.path.basename(mp3_filepath)}' 파일을 '{os.path.basename(txt_filepath)}'에 따라 분리합니다...")

    for i, line in enumerate(lines):
        line = line.strip()
        if not line:
            continue

        # '시작시간/제목/음악가' 형식으로 분리
        parts = line.split('/', 2) # 최대 2번만 분리하여 음악가 이름에 '/'가 있어도 오류 방지
        if len(parts) < 3:
            print(f"경고: {i+1}번째 줄 '{line}'이(가) 형식이 잘못되어 건너뜁니다. 형식: 시작시간/제목/음악가")
            continue

        try:
            start_time_str = parts[0].strip()
            title = parts[1].strip()
            artist = parts[2].strip()

            start_ms = time_to_milliseconds(start_time_str)

            if start_ms >= total_audio_length_ms:
                print(f"경고: {i+1}번째 줄 ('{line}')을(를) 건너뜠습니다. 시작 시간이 오디오 길이보다 깁니다.")
                continue

            segments_info.append((start_ms, title, artist))

        except ValueError as ve:
            print(f"오류: {i+1}번째 줄 '{line}'에서 시간 또는 정보 구문 분석 중 오류 발생 - {ve}")
        except Exception as e:
            print(f"오류: {i+1}번째 줄 '{line}'에서 예상치 못한 오류 발생 - {e}")

    # 시작 시간을 기준으로 정렬 (혹시 텍스트 파일 순서가 뒤죽박죽일 경우를 대비)
    segments_info.sort(key=lambda x: x[0])

    # 각 세그먼트 추출 및 태그 추가
    for i, (start_ms, title, artist) in enumerate(segments_info):
        # 다음 세그먼트의 시작 시간을 현재 세그먼트의 끝 시간으로 설정
        # 마지막 세그먼트의 경우 오디오의 끝까지
        if i + 1 < len(segments_info):
            end_ms = segments_info[i+1][0]
        else:
            end_ms = total_audio_length_ms

        # 안전 장치: 시작 시간이 종료 시간보다 크거나 같을 경우 (논리적 오류 방지)
        if start_ms >= end_ms:
            print(f"경고: 세그먼트 '{title}' ({ms_to_hms(start_ms)})의 시작 시간이 종료 시간 ({ms_to_hms(end_ms)})보다 크거나 같아 건너뜠습니다.")
            continue

        # 파일명은 제목으로 사용 (Windows/Linux/macOS 파일명 규칙에 맞게 특수문자 제거 또는 치환)
        # 예: '/', ':', '*', '?', '"', '<', '>', '|'
        invalid_chars = r'[\/:*?"<>|]'
        safe_filename = "".join(c if c.isalnum() or c in [' ', '-', '_', '.', ','] else '_' for c in title) # 더 유연하게 허용
        # re 모듈을 사용하는 것이 더 좋지만, 여기서는 간단한 예시로 대체합니다.
        # import re
        # safe_filename = re.sub(invalid_chars, '_', title)


        output_filename = safe_filename + ".mp3"
        output_filepath = os.path.join(output_directory, output_filename)

        try:
            segment = audio[start_ms:end_ms]
            segment.export(output_filepath, format="mp3")
            print(f"  내보내기 완료: {output_filepath} (시작: {ms_to_hms(start_ms)}, 종료: {ms_to_hms(end_ms)})")

            # ID3 태그 추가
            try:
                mp3_file = MP3(output_filepath)
                if not mp3_file.tags:
                    mp3_file.add_tags()

                # TIT2: 제목 (Title)
                mp3_file.tags.add(TIT2(encoding=3, text=[title]))
                # TPE1: 아티스트 (Artist) - '참여 음악가'로 사용
                mp3_file.tags.add(TPE1(encoding=3, text=[artist]))
                # TPE2: 앨범 아티스트 (Album Artist) - 필요하다면 추가 가능
                # mp3_file.tags.add(TPE2(encoding=3, text=["Massimo Faraò Trio"])) # 예시

                mp3_file.save()
                print(f"  태그 추가 완료: '{title}' (아티스트: '{artist}')")
            except Exception as tag_e:
                print(f"경고: '{output_filepath}' 파일에 ID3 태그 추가 중 오류 발생: {tag_e}")

        except Exception as e:
            print(f"오류: '{output_filename}' 파일 내보내기 중 오류 발생 - {e}")

    print("\nMP3 분리 및 태그 추가 완료!")

if __name__ == "__main__":
    # --- 설정 ---
    # 실제 파일 경로와 텍스트 파일명으로 반드시 변경하세요!
    input_mp3_file = "E:/temp/Sound Test/aa.mp3"
    input_txt_file = "E:/temp/Sound Test/aa.txt"
    output_folder = "E:/temp/Sound Test/aa_dir" # 분리된 파일이 저장될 새 폴더를 지정하는 것이 좋습니다.

    # --- 분리 함수 실행 ---
    split_mp3_by_txt(input_mp3_file, input_txt_file, output_folder)

    print(f"\n'{output_folder}' 디렉토리에서 분리되고 태그가 추가된 MP3 파일을 확인하세요.")
    print("MP3 파일 경로와 텍스트 파일 내용을 실제 사용 환경에 맞게 변경하는 것을 잊지 마세요.")
728x90
반응형

댓글