유튜브 쇼츠를 Python으로 자동 생성하는 방법 (실제 운영 중인 자동화 파이프라인 공개)

NAS에서 유튜브 쇼츠 자동 생성 파이프라인을 구성한 과정을 정리합니다. YouTube API 수집부터 분석, 대본, 음성 및 영상 생성까지 살펴봅니다.

유튜브 쇼츠를 직접 촬영하지 않고 Python으로 자동 생성하도록 구성한 시스템의 운영 사례입니다.
UGREEN DXP4800PLUS NAS에서 YouTube API로 트렌드 데이터를 수집하고,
성장률 분석 → 주제 선정 → 대본 생성 → 음성 합성 → 영상 합성까지 자동으로 처리합니다.

사례 기록에서 확인한 범위는 유튜브 영상 수집과 30분 주기 분석, 초안 저장 후 수동 검토까지입니다. 사례의 수집 영상은 약 1,100개이며, 관련 성장률 계산 글에는 COUNT 결과 1,095개가 기록돼 있습니다. 700개 쿼터 사례와의 집계 시점·범위는 기록돼 있지 않아 수치를 직접 비교할 수 없습니다.
이 글은 그 파이프라인 전체 구조와 실제로 막혔던 부분들을 정리합니다.


전체 파이프라인 구조

YouTube API
↓
PostgreSQL (영상 데이터 저장)
↓
성장률 분석 (6시간 / 24시간)
↓
AI 언어 모델 (대본 생성)
↓
Edge-TTS (한국어 음성 합성)
↓
FFmpeg (자막 + 배경 합성)
↓
drafts/ 폴더 저장
↓
수동 검토
↓
YouTube 업로드

각 단계가 독립적인 Python 스크립트로 구성돼 있어서
문제가 생긴 단계만 수정하고 나머지는 그대로 쓸 수 있습니다.


1. PostgreSQL에서 트렌드 주제 선정

단순히 조회수 높은 영상이 아니라 6시간 성장률 기준으로 지금 뜨는 주제를 고릅니다.
성장률 계산 구조는 이전 글
PostgreSQL로 YouTube 조회수 성장률 계산하기에서 다뤘습니다.

import asyncpg

async def get_trending_topics():
    conn = await asyncpg.connect(DATABASE_URL)
    rows = await conn.fetch("""
        SELECT v.title, v.content_cluster,
               ROUND((latest.view_count - older.view_count)::numeric
                     / NULLIF(older.view_count, 0) * 100, 2) AS growth_6h
        FROM videos v
        JOIN LATERAL (
            SELECT view_count, recorded_at
            FROM video_snapshots
            WHERE video_id = v.video_id
            ORDER BY recorded_at DESC
            LIMIT 1
        ) latest ON true
        JOIN LATERAL (
            SELECT view_count
            FROM video_snapshots
            WHERE video_id = v.video_id
              AND recorded_at <= latest.recorded_at - INTERVAL '6 hours'
            ORDER BY recorded_at DESC
            LIMIT 1
        ) older ON true
        WHERE v.content_cluster = 'AI_AUTOMATION'
          AND older.view_count > 0
        ORDER BY growth_6h DESC
        LIMIT 5
    """)
    await conn.close()
    return [dict(r) for r in rows]

content_cluster = 'AI_AUTOMATION'으로 필터링해서
채널 방향성과 맞는 주제만 고릅니다.


2. AI 언어 모델로 대본 생성

주제가 정해지면 AI 언어 모델로 60초 분량의 쇼츠 대본을 생성합니다.

import httpx, os

async def generate_script(topic: str) -> str:
    # OpenRouter를 통해 AI 언어 모델 호출 (모델명은 env로 관리)
    api_key = os.getenv("OPENROUTER_API_KEY")
    prompt = f"""
다음 주제로 유튜브 쇼츠 대본을 작성해줘.
주제: {topic}

조건:
- 60초 분량 (약 250자)
- 첫 3초 안에 핵심 내용 언급
- 구어체로 작성
- 영어 단어는 한국어 발음으로 변환 (API → 에이피아이)
"""
    async with httpx.AsyncClient(timeout=30) as client:
        resp = await client.post(
            "https://openrouter.ai/api/v1/chat/completions",
            headers={"Authorization": f"Bearer {api_key}"},
            json={
                "model": os.getenv("SCRIPT_MODEL"),
                "messages": [{"role": "user", "content": prompt}],
                "max_tokens": 500,
            },
        )
    return resp.json()["choices"][0]["message"]["content"]

영어 단어를 한국어 발음으로 변환하는 조건이 중요합니다.
Edge-TTS가 영어 단어를 만나면 발음이 어색해지기 때문입니다.
이 부분은 이전 글
Edge-TTS 한국어 음성 생성 시 발음 오류와 자막 싱크 문제 해결 방법에서 자세히 다뤘습니다.


3. Edge-TTS로 음성과 SRT 자막 생성

import asyncio
import edge_tts

async def text_to_speech(text: str, audio_path: str, subtitle_path: str):
    communicate = edge_tts.Communicate(text, "ko-KR-SunHiNeural", rate="+20%")
    submaker = edge_tts.SubMaker()
    with open(audio_path, "wb") as audio_file:
        async for chunk in communicate.stream():
            if chunk["type"] == "audio":
                audio_file.write(chunk["data"])
            elif chunk["type"] in ("WordBoundary", "SentenceBoundary"):
                submaker.feed(chunk)
    with open(subtitle_path, "w", encoding="utf-8") as sub_file:
        sub_file.write(submaker.get_srt())

asyncio.run(text_to_speech(script, "output/audio.mp3", "output/subtitles.srt"))

같은 스트림에서 오디오와 경계 이벤트를 함께 처리해 SRT 타임라인을 만듭니다. 스트림을 소비한 뒤 같은 객체로 다시 저장하지 않습니다.


4. FFmpeg으로 영상·자막 합성

import subprocess

def create_video(audio_path: str, bg_image: str, subtitle_path: str, output_path: str):
    cmd = [
        "ffmpeg", "-loop", "1", "-i", bg_image, "-i", audio_path,
        "-c:v", "libx264", "-c:a", "aac", "-b:a", "192k",
        "-pix_fmt", "yuv420p", "-shortest",
        "-vf", f"scale=1080:1920,subtitles={subtitle_path}",
        "-r", "30", output_path,
    ]
    subprocess.run(cmd, check=True)

create_video("output/audio.mp3", "background.jpg", "output/subtitles.srt", "output/short.mp4")

이 파이프라인은 세로형 출력으로 1080×1920을 선택합니다. YouTube 공식 안내의 현재 분류 기준은 9:16 해상도를 필수로 요구하는 것이 아니라, 2024년 10월 15일 이후 업로드된 정사각형 또는 세로 비율의 3분 이하 영상을 Shorts로 분류합니다. 따라서 1080×1920은 이 예제의 출력 규격이지 Shorts의 유일한 필수 규격이 아닙니다.


실제로 막혔던 문제들

문제 1: 자막 싱크 어긋남

초기에는 음성 길이와 자막 타임라인을 따로 계산해 싱크가 어긋났습니다. 출력 fps를 30으로 통일하는 것과 별개로, 자막 타임스탬프는 Edge-TTS의 경계 이벤트에서 SRT로 생성하고 FFmpeg의 subtitles 필터로 합성하도록 바꿨습니다.

문제 2: 영어 단어 발음 오류

“FastAPI”를 Edge-TTS가 어색하게 읽었습니다.
대본 생성 프롬프트에 영어 단어 한국어 변환 조건을 추가해서 해결했습니다.

문제 3: 생성 영상이 세로형이 아님

이 자동화 파이프라인의 출력 규격을 9:16(1080×1920)로 통일했습니다. YouTube Shorts 자체는 정사각형 또는 세로 비율의 3분 이하 영상도 대상이므로, 1080×1920은 필수 규격이 아니라 이 파이프라인의 선택값입니다.


사례에 기록된 운영 결과

  • 수집 영상: 약 1,100개
  • 저장 스냅샷: 약 74,000개
  • 트렌드 분석 주기: 30분
  • 쇼츠 1개 생성 시간: 평균 2~3분
  • 생성 결과물: drafts/ 폴더 저장 후 수동 검토 → 업로드
  • 채널: AI자동화휴게소

완전 자동 업로드는 아직 적용하지 않았습니다.
대본 품질 검토가 필요한 단계라 지금은 생성 → 검토 → 수동 업로드 흐름을 유지합니다.


마무리

Python으로 쇼츠를 자동 생성하는 건 기술적으로 어렵지 않습니다.
핵심은 트렌드 데이터 → 주제 선정 → 대본 → 음성 → 영상의 흐름을 자동화하는 것입니다.
각 단계를 독립적으로 구성해두면 한 단계에 문제가 생겨도 전체를 다시 짜지 않아도 됩니다.

관련 글: