[Python] 오디오북을 위한 '물 흐르는' 동기화 스크롤 자막 만들기 (ASS 자막 마스터하기)

2026. 1. 16.

 

오늘은 파이썬(Python)을 이용해 오디오북이나 팟캐스트 같은 오디오 콘텐츠에 한 단계 높은 수준의 자막을 입히는 방법을 소개해 드리려고 합니다.

 

단순히 텍스트만 표시하는 자막이 아닌, 현재 재생되는 음성에 맞춰 부드럽게 스크롤되며 이전/다음 내용까지 보여주는, 마치 전문 다큐멘터리나 노래방 가사처럼 '물 흐르듯' 흘러가는 자막을 만들어 보겠습니다.

 

왜 일반적인 방법은 한계가 있을까?

처음에는 간단하게 자막을 구현하려고 시도합니다.

  1. 단순 자막: 그냥 텍스트만 보여주니 오디오북의 흐름을 표현하기에 너무 정적이고 지루합니다.
  2. 단순 스크롤: 모든 자막을 하나의 긴 텍스트로 합쳐 처음부터 끝까지 스크롤 시키면, 음성과 자막의 타이밍이 전혀 맞지 않는 문제가 발생합니다.
  3. 개별 라인 스크롤: 각 자막을 개별적으로 스크롤 시키면, 자막이 사라졌다가 다시 나타나는 '깜빡임' 현상 때문에 흐름이 끊기고 매우 부자연스럽습니다.

우리가 원하는 것은 음성 동기화부드러운 연속성, 이 두 가지를 모두 만족시키는 것입니다.

최종 목표: '무빙 윈도우(Moving Window)' 기법

이 모든 문제를 해결하는 열쇠는 바로 '무빙 윈도우' 기법입니다.

  • 고정된 창(Window): 화면 상단에 3줄(또는 5줄 등)을 보여주는 가상의 '창'을 만듭니다.
  • 활성 라인(Active Line): 이 창의 특정 줄(예: 두 번째 줄)을 현재 음성이 나오는 '활성 라인'으로 지정합니다.
  • 부드러운 전환: 자막이 바뀔 때마다, 창 안의 내용이 한 줄씩 위로 스르륵 올라가며 새로운 내용이 아래에서 나타납니다.

이 마법 같은 효과의 비밀은 완벽한 타이밍 제어와 ASS 자막의 강력한 \fad (페이드) 태그에 있습니다. 이전 자막이 사라지는 동시에 다음 자막이 나타나는 '크로스페이드' 효과를 만들어, 시청자는 창이 교체된다는 사실조차 인지하지 못하고 내용만 부드럽게 흘러가는 것처럼 느끼게 됩니다.

준비물: Python 라이브러리 설치

이 모든 것을 자동화하기 위해 강력한 자막 처리 라이브러리인 pysubs2가 필요합니다. 터미널이나 명령 프롬프트에 아래 명령어를 입력하여 설치해주세요.

pip install pysubs2

최종 완성 코드: 모든 기능이 통합된 Python 스크립트

아래 코드는 원본 SRT 파일의 시간 간격을 자동으로 보정하고, '무빙 윈도우' 스크롤 효과를 적용하는 모든 기능이 포함된 최종 버전입니다.

import pysubs2

def create_synchronized_scrolling_subtitles(
    input_srt_path: str,
    output_ass_path: str,
    visible_lines: int = 3,
    sync_line_index: int = 1,
    font_size: int = 48,
    top_margin: int = 150,
    fade_duration: int = 250
):
    """
    SRT 파일을 읽어, 타이밍이 동기화된 연속 스크롤 ASS 자막을 생성합니다.

    Args:
        input_srt_path (str): 원본 SRT 파일 경로.
        output_ass_path (str): 저장할 ASS 파일 경로.
        visible_lines (int): 화면에 동시에 표시할 총 라인 수.
        sync_line_index (int): 음성과 동기화할 라인의 인덱스 (0부터 시작, 1은 두 번째 줄).
        font_size (int): 자막 폰트 크기.
        top_margin (int): 화면 상단 여백.
        fade_duration (int): 페이드 인/아웃 지속 시간 (밀리초).
    """
    print(f"'{input_srt_path}' 파일을 불러옵니다...")
    try:
        subs = pysubs2.load(input_srt_path, encoding="utf-8")
    except Exception as e:
        print(f"파일을 불러오는 중 오류가 발생했습니다: {e}")
        return

    if not subs or sync_line_index >= visible_lines:
        print("경고: 자막이 비어있거나 설정값이 잘못되었습니다.")
        return

    # --- 1. 자막 간 타이밍을 완벽하게 동기화 (가장 중요한 전처리) ---
    for i in range(len(subs) - 1):
        subs[i].end = subs[i+1].start

    # --- 2. 새로운 ASS 파일 및 스타일 생성 ---
    ass_out = pysubs2.SSAFile()
    ass_out.info = subs.info.copy()
    play_res_x = int(ass_out.info.get('PlayResX', '1920'))

    line_spacing_multiplier = 1.3
    line_height = font_size * line_spacing_multiplier

    style = pysubs2.SSAStyle(
        name='SyncMultiLineScroll', fontname='맑은 고딕', fontsize=font_size,
        primarycolor=pysubs2.Color(255, 255, 255), outlinecolor=pysubs2.Color(0, 0, 0),
        backcolor=pysubs2.Color(0, 0, 0, 128), bold=True, borderstyle=1, outline=2,
        shadow=1, alignment=8,  # 상단 중앙 정렬
        marginl=int(play_res_x * 0.1), marginr=int(play_res_x * 0.1)
    )
    ass_out.styles['SyncMultiLineScroll'] = style

    print("스크롤 이벤트를 생성합니다...")
    # --- 3. 각 라인을 순회하며 '무빙 윈도우' 이벤트 생성 ---
    for i, current_line in enumerate(subs):
        window_texts = []
        for j in range(visible_lines):
            line_index = i - sync_line_index + j
            if 0 <= line_index < len(subs):
                # 원본 SRT의 줄바꿈(\n)을 공백으로 처리
                window_texts.append(subs[line_index].text.replace('\n', ' '))
            else:
                window_texts.append("")

        # ASS 형식의 줄바꿈(\N)으로 텍스트 블록 생성
        event_text_block = "\\N".join(window_texts)

        # 시작 Y좌표(y1): 윈도우의 최상단이 화면 상단 여백에 위치
        y1 = top_margin
        # 종료 Y좌표(y2): 윈도우가 정확히 한 줄 높이만큼 위로 이동
        y2 = y1 - line_height

        center_x = play_res_x / 2

        # ASS 태그 조합
        effect = (
            f"\\an8"  # 정렬 기준: 상단 중앙
            f"\\fad({fade_duration}, {fade_duration})"
            f"\\move({center_x}, {y1}, {center_x}, {y2})" # 절대 좌표로 이동
        )

        event_text = f"{{{effect}}}{event_text_block}"

        event = pysubs2.SSAEvent(
            start=current_line.start,
            end=current_line.end,
            text=event_text,
            style='SyncMultiLineScroll'
        )
        ass_out.events.append(event)

    # --- 4. 결과 파일 저장 ---
    try:
        ass_out.save(output_ass_path)
        print(f"성공! 결과가 '{output_ass_path}' 파일로 저장되었습니다.")
    except Exception as e:
        print(f"파일 저장 중 오류가 발생했습니다: {e}")

# --- 스크립트 사용 예시 ---
if __name__ == "__main__":
    # 변환할 원본 SRT 파일과 저장할 ASS 파일 이름을 지정합니다.
    INPUT_FILE = "my_audiobook.srt"
    OUTPUT_FILE = "my_audiobook_scroll.ass"

    # 예제용 SRT 파일 생성 (실제로는 이 부분을 주석 처리하고 자신의 파일을 사용하세요)
    sample_srt_content = """1
00:00:00,431 --> 00:00:05,731
이것은 다윗의 자손이요 아브라함의 자손이신 예수 그리스도의 족보입니다.

2
00:00:06,621 --> 00:00:14,481
아브라함은 이삭을 낳고, 이삭은 야곱을 낳았으며, 야곱은 유다와 그의 형제들을 낳았습니다.

3
00:00:15,581 --> 00:00:23,591
유다는 다말에게서 베레스와 세라를 낳고, 베레스는 헤스론을 낳았으며, 헤스론은 람을 낳았습니다.
"""
    with open(INPUT_FILE, "w", encoding="utf-8") as f:
        f.write(sample_srt_content)

    # 함수 호출!
    create_synchronized_scrolling_subtitles(INPUT_FILE, OUTPUT_FILE)

스크립트 사용 방법

  1. 위 코드를 scroll_creator.py 와 같은 이름으로 저장합니다.
  2. 같은 폴더에 변환하고 싶은 원본 자막 파일(예: my_audiobook.srt)을 준비합니다.
  3. 코드 하단의 if __name__ == "__main__" 부분에서 INPUT_FILEOUTPUT_FILE 변수를 자신의 파일 이름에 맞게 수정합니다.
  4. 터미널에서 python scroll_creator.py 명령어를 실행합니다.
  5. 지정한 OUTPUT_FILE 이름(예: my_audiobook_scroll.ass)으로 최종 결과 파일이 생성됩니다! 이 파일을 영상/오디오와 함께 플레이어에서 재생하면 됩니다.

 

꼭 확인해야 할 점: 자막 한 줄의 길이

이 기법은 SRT 자막의 한 라인이 시각적으로도 한 줄을 차지한다고 가정합니다. 만약 SRT의 특정 라인이 너무 길어서 화면에서 두 줄로 표시된다면, 계산이 어긋나 자막이 겹칠 수 있습니다.

스크립트를 실행하기 전, 원본 SRT 파일의 각 라인이 너무 길지 않도록 미리 정리해주는 것이 좋습니다. (한글 기준 25~30자 내외 권장)

 

마치며

이제 여러분도 파이썬 단 몇 줄로 자신의 오디오 콘텐츠에 전문적인 스크롤 자막을 입힐 수 있게 되었습니다.

코드의 visible_lines, font_size, top_margin 같은 변수들을 조절하며 자신만의 스타일을 찾아보는 재미도 느껴보시길 바랍니다.

 

댓글