nginx로 HLS 정적 서빙 (feat. yt-dlp)

강정우·2025년 10월 17일
0

기타

목록 보기
2/4
post-thumbnail

0. 서론

비록 정책 문제와 맞물려 채택되지 못한 아이디어이지만 굉장히 참신한 아디어라 소개해본다.

1. 아이디어

  1. 특정 주소 접속해서 라이브 변환기 가 켜져있는지 확인.
  2. 대상 채널 코드youtube api에 주고, 켜져 있는 라이브 방송이 있는지 알아본다. (100 가스 소요.)
    --> response 라이브 방송 클립코드
  3. 라이브 방송 클립코드가 live인지 체크한다. (1 가스 소요.)
  4. 라이브 방송 채널 코드를 주고, yt-dlp로 돌린걸 ffmpeg으로 넘겨준다.
    3.1 yt-dlp로 hls 주소를 확인한다.
    3.2 방송을 ffmpeg으로 포장 -> response ffmpeg으로 넘겨준 master.m3u8 경로
  5. nginx가 master.m3u8을 서빙
  6. 2번, 3번을 서비스로 등록

2. Youtube API

1) youtube API 설정

  1. gcp 콘솔에서 프로젝트를 하나 만들고 API 및 서비스에 접근한다.

  2. 사용자 인증 정보 탭에 들어가서 API 키를 생성한다.

이때, API키 수정을 누른 다음

YouTube Data API v3 만 선택하여 사용하도록 하자.

2) youtube API 사용

Search: list 를 사용해야한다. (관련 문서)

요청 req url 예시

https://www.googleapis.com/youtube/v3/search?part=snippet&q=@CHANNEL_HANDLE&eventType=live&type=video&key=YOUR_KEY

4. 구현

1) HLS 패스스루

“라이트 리스팀” — YouTube Live → (내 서버) HLS 패스스루
유튜브의 라이브 HLS(m3u8)를 받아서, 내 도메인에서 HLS로 재공급하는 방식이다.

이 방식의 문제는 cookie, 정책 등의 문제로 아쉽게도 탈락 되었다.
일일이 수동으로 해야할 경우가 많고 문제를 일으킬 요소도 굉장히 많았기 때문이다.

장점은 Youtube Live Url 을 인코딩 없이 -c copy로 리패키징(패스스루) 하면 CPU 부담 거의 없고 Youtube Live Url은 만료 토큰이 있으므로 주기적으로 새 URL을 갱신하는 작은 스크립트만 추가하면 되기 때문에 굉장히 간단하다.

하지만 담점으로 약관/저작권 이슈를 스스로 관리해야 하고(특히 외부 공개 재배포 시), URL 만료 시 자동 재연결 로직이 필요하다.

2) 최소 구현 예시

0. 필수 lib 설치

pipx install yt-dlp
source ~/.bashrc
which yt-dlp

여기서 yt-dlp 가 잘 나오는 것을 확인.

1. 유튜브 라이브 스트리밍 HLS(m3u8) URL 얻기

유튜브에서 방송 중인 채널의 HLS 주소를 직접 얻기 위해 youtube download player 를 사용

yt-dlp --cookies 쿠키경로 -g "https://www.youtube.com/watch?v=스트리밍ID"

# ex)
yt-dlp --cookies /opt/yt-restream/cookies.txt -g "https://www.youtube.com/watch?v=스트리밍ID"

2. ffmpeg로 재인코딩 & HLS 세그먼트 생성

유튜브의 HLS 스트림을 ffmpeg가 받아서, 우리 서버에서 쓸 수 있는 HLS로 변환하는 작업이다.

ffmpeg \
    -i "$(yt-dlp -g https://www.youtube.com/watch?v=스트리밍ID)" \
    -c copy \
    -f hls \
    -hls_time 4 \
    -hls_list_size 5 \
    -hls_flags delete_segments \
    /var/www/html/stream/index.m3u8
    
    
# ex)
ffmpeg -i "$(yt-dlp --cookies /opt/yt-restream/cookies.txt -g https://www.youtube.com/watch?v=스트리밍ID)" \
    -c copy -f hls -hls_time 4 -hls_list_size 5 -hls_flags delete_segments \
    /var/www/html/stream/index.m3u8

설명:
-c copy → 재인코딩 없이 원본 그대로 복사 (CPU 부담 ↓)
-hls_time 4 → 세그먼트 길이 4초
-hls_list_size 5 → m3u8에 최신 5개 세그먼트만 유지
-hls_flags delete_segments → 오래된 ts 파일 삭제
/var/www/html/stream/index.m3u8 → Nginx 같은 웹서버에서 접근 가능한 경로

  • 참고로 이때, 지정한 path 에 폴더가 있어야한다.

3. (옵션) 권한 설정하기

sudo mkdir -p /opt/yt-restream
sudo nano /opt/yt-restream/restream.sh
sudo chmod +x /opt/yt-restream/restream.sh

4. nginx 설정 해주고

이때, CORS 필요한 경우 nginx에 add_header Access-Control-Allow-Origin *; 등 옵션 추가

5. 이제 위 수동 테스트를 바탕으로 자동화 sh 파일 작성

#!/usr/bin/env bash
set -Eeuo pipefail

VIDEO_URL="https://www.youtube.com/watch?v=NJUjU9ALj4A"
COOKIES="/opt/yt-restream/cookies.txt"
OUTPUT_DIR="/var/www/html/stream"

mkdir -p "$OUTPUT_DIR"

while true; do
    echo "[$(date)] Fetching new m3u8 URL..."
    M3U8_URL=$(/home/ubuntu/.local/bin/yt-dlp --cookies "$COOKIES" -g "$VIDEO_URL")

    echo "[$(date)] Starting ffmpeg restream..."
    ffmpeg \
        -loglevel error \
        -i "$M3U8_URL" \
        -c copy \
        -f hls \
        -hls_time 4 \
        -hls_list_size 5 \
        -hls_flags delete_segments \
        "$OUTPUT_DIR/index.m3u8"

    echo "[$(date)] ffmpeg stopped. Restarting in 5 seconds..."
    sleep 5
done

이때, ffmpeg 옵션으로 -hls_time 2, -hls_list_size 6 이면 보통 6~12초. 더 낮추면 끊김 위험이 높다.

6. 서비스 등록

systemd 서비스 파일 작성

[Unit]
Description=YouTube to HLS Restream Service
After=network.target

[Service]
Type=simple
User=ubuntu
ExecStart=/opt/yt-restream/restream.sh
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

7. 실행 및 로그

sudo systemctl daemon-reload
sudo systemctl enable 서비스이름
sudo systemctl start 서비스이름

로그 확인

journalctl -u 서비스이름 -f

결론. 이 아이디어가 채택이 안 된 이유.

트랜스코딩 없이 패스스루(-c copy)라 CPU는 가볍지만, 원본 코덱(보통 H.264/MP4 또는 VP9/WEBM)에 따라 일부 레거시 플레이어 호환성에 차이가 날 수 있다. 이때, 문제가 생기면 -c:v h264 -c:a aac 로 트랜스코딩을 고려하면 되는데 대신 CPU/GPU 코스트가 상승한다.
참고로 CDN을 앞단에 두면 대규모 시청자도 안정적으로 처리가 가능하다.

무튼 이 아이디어가 채택이 안 된 이유는 요청 빈도(yt-dlp & ffmpeg) 때문이다.
동작 과정이
1. yt-dlp
yt-dlp -g 명령을 실행하는 순간, 유튜브 페이지를 1~2번 요청해서 실제 m3u8 주소를 뽑습는다.
평상시에 1초마다 계속 호출하는 건 아니고, 명령 실행할 때만 요청한다.
m3u8 URL은 보통 몇 분~몇 시간 유효하지만, 유튜브 라이브는 1시간 이내 만료되는 경우가 많다.
→ 그래서 주기적으로 새로 받아와야 함 (예: 30분~1시간마다)
2. ffmpeg
그리고 사실 여기가 문제인데 ffmpeg는 yt-dlp가 뽑아준 m3u8 URL을 읽어오는데,
이건 내부적으로 유튜브의 세그먼트(ts) 파일을 받아오는 HTTP 요청을 세그먼트 길이(내 경우 12초)마다 보낸다.
그럼 youtube 측에서는 요청을 너무 많이 보내서 429 too many req 에러를 보낸다.

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글