Skip to content

Commit fbe1b24

Browse files
SOIVclaude
andcommitted
docs: 영상 다운로더 모듈 설계 문서 보강
- 다운로드 엔진 분류 및 우선순위 정리 (yt-dlp → streamlink → ffmpeg → 커스텀 추출기 → capture) - capture 엔진 추가: Puppeteer --disable-gpu + FFmpeg 화면 캡처 (Abema 등 DRM 라이브 스트림 대응, 최후 수단) - SNI/DPI 차단 우회 섹션 추가 GoodbyeDPI / 유니콘 HTTPS / zapret 비교 및 Linux 기준 zapret 채택 - VPN 통합 섹션 추가 SOCKS5 프록시 경유로 웹 UI와 다운로드 트래픽 분리 WireGuard / OpenVPN 설정 파일 자동 감지 Surfshark / Mullvad / ProtonVPN / NordVPN 지원 현황 - 커스텀 TS 추출기: Hitomi Python 추출기 포팅 방침 정리 - 바이너리 번들링 구조 확정 (bin-manager.ts 자동 다운로드) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8285e00 commit fbe1b24

1 file changed

Lines changed: 160 additions & 17 deletions

File tree

docs/v2_FINANCIAL-LEDGER/_modules_for-future/04-video-downloader.md

Lines changed: 160 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Streamlink / yt-dlp 기반의 영상 다운로드 관리 모듈.
1515

1616
### 1. 다운로드 유형
1717

18-
- **일반 다운로드**: URL 입력 → yt-dlp로 영상 다운로드 (YouTube, Twitch VOD 등)
18+
- **일반 다운로드**: URL 입력 → yt-dlp / 커스텀 추출기로 영상 다운로드
1919
- **라이브 스트림 녹화**: Streamlink 기반 실시간 스트림 캡처 및 저장
2020
- **예약 다운로드**: 지정 시각에 자동 시작 (방송 시작 시간 예약 등)
2121
- **배치 다운로드**: URL 목록을 한 번에 등록, 순차 또는 병렬 처리
@@ -46,28 +46,166 @@ Streamlink / yt-dlp 기반의 영상 다운로드 관리 모듈.
4646

4747
## 기술 구조 (설계안)
4848

49-
### 백엔드
49+
### 다운로드 엔진 분류
50+
51+
URL이 들어오면 `runner.ts`가 사이트를 판별하여 적절한 엔진으로 라우팅한다.
52+
53+
| 엔진 | 처리 대상 | 비고 |
54+
|---|---|---|
55+
| **yt-dlp** | YouTube, Twitch VOD, NicoNico, Bilibili 등 메이저 사이트 | standalone 바이너리 번들 |
56+
| **streamlink** | Twitch 라이브, SOOP(구 AfreecaTV) 등 라이브 스트림 | standalone 바이너리 번들 |
57+
| **ffmpeg** | m3u8/HLS/DASH URL을 직접 노출하는 사이트 | ffmpeg-static npm 패키지 |
58+
| **커스텀 TS 추출기** | 위 세 엔진으로 커버 안 되는 사이트 | 모듈 내 TypeScript로 직접 구현 |
59+
| **capture** | yt-dlp/streamlink가 막히는 DRM 라이브 스트림 (최후 수단) | Puppeteer `--disable-gpu` + FFmpeg 화면 캡처 |
60+
61+
ffmpeg는 HLS/DASH 스트림 URL만 알면 직접 녹화·저장이 가능하므로,
62+
커버 범위가 생각보다 넓다 (`ffmpeg -i "https://...m3u8" -c copy output.mp4`).
63+
64+
**엔진 우선순위 (동일 URL에 대해):**
65+
```
66+
yt-dlp (쿠키 포함) → streamlink → ffmpeg → 커스텀 추출기 → capture
67+
```
68+
capture는 위 모든 엔진이 실패한 경우의 최후 수단. 실시간 재생 속도로만 캡처 가능하므로 라이브 공연 등 1회성 방송 녹화 용도에 한해 사용.
69+
70+
**Abema 등 지역 제한 + DRM 사이트 처리 흐름:**
71+
1. VPN은 사용자가 서버 네트워크 레벨에서 미리 설정
72+
2. yt-dlp + 브라우저 쿠키로 스트림 직접 추출 시도 (yt-dlp에 Abema 추출기 내장)
73+
3. 라이브 DRM으로 yt-dlp가 막히면 capture 엔진으로 폴백
74+
- Puppeteer `--disable-gpu` 플래그로 하드웨어 가속 OFF
75+
- CPU 소프트웨어 렌더링으로 프레임이 시스템 메모리를 경유
76+
- FFmpeg(`x11grab`/`gdigrab`)로 가상 디스플레이 캡처
77+
78+
### 커스텀 TS 추출기
79+
80+
Hitomi Downloader의 Python 추출기(`.py`)를 **로직 참고용**으로 활용하되,
81+
실제 구현은 TypeScript로 새로 작성한다.
82+
83+
**이유:**
84+
- Python 런타임 / PyInstaller 빌드 파이프라인 불필요
85+
- Hitomi 원본은 PyQt UI(`cw`)와 자체 유틸에 강하게 결합되어 있어 재사용 어려움
86+
- 원본 이슈 트래커에 사이트 정책 변경에 따른 버그가 다수 존재 → 직접 관리가 유지보수에 유리
87+
88+
**추출기 구조 (안):**
89+
```
90+
backend/
91+
extractors/
92+
base.ts ← 추출기 인터페이스 (canHandle(url), extract(url) → StreamUrl)
93+
niconico.ts ← NicoNico 전용 HTML 파싱 + API 호출
94+
pixiv.ts
95+
...
96+
```
97+
98+
각 추출기는 "페이지 HTML/API 파싱 → 실제 스트림 URL 추출"만 담당하고,
99+
URL을 얻은 뒤 실제 다운로드는 ffmpeg 또는 yt-dlp에 위임한다.
100+
101+
### SNI/DPI 차단 우회
102+
103+
ISP가 SNI 검사로 특정 도메인을 차단하는 경우, 패킷 조작으로 우회한다.
104+
목적과 원리는 동일하며 **패킷을 가로채기 위해 OS에서 어떤 훅을 쓰는지**만 다르다.
105+
106+
**공통 원리:**
107+
TLS ClientHello 패킷의 SNI 필드를 DPI 장비가 읽지 못하도록 조작한다.
108+
주요 기법: 패킷 분할(fragmentation), TTL 조작, TCP 역순 전송 — 셋 다 동일한 기법을 조합해 사용.
109+
110+
| 도구 | 플랫폼 | OS 훅 방식 | 비고 |
111+
|---|---|---|---|
112+
| **GoodbyeDPI** | Windows | WinDivert 드라이버로 패킷 가로채기 | 오픈소스, 설정 자유도 높음 |
113+
| **유니콘 HTTPS** | Windows / Android | 동일 (WinDivert) | 한국산, 한국 통신사 차단 패턴에 맞게 튜닝 |
114+
| **zapret** | **Linux** / macOS | nftables/iptables NFQUEUE | Linux 표준, GoodbyeDPI와 동일 원리 |
115+
116+
**모듈 적용:**
117+
서버가 Linux에서 동작하므로 **zapret**을 사용.
118+
GoodbyeDPI / 유니콘 HTTPS는 Windows 전용이므로 제외.
119+
120+
```
121+
SNI 차단 사이트 접근 시:
122+
zapret 활성화 여부 확인
123+
→ 활성 : 그대로 다운로드 진행
124+
→ 비활성 : UI에 "SNI 차단 우회 비활성 — 설정에서 zapret 활성화" 안내
125+
```
126+
127+
**권한:**
128+
zapret은 Linux 커널 netfilter를 사용하므로 `CAP_NET_ADMIN` 권한 필요.
129+
- Docker: `--cap-add NET_ADMIN`
130+
- systemd: `AmbientCapabilities=CAP_NET_ADMIN`
131+
권한이 없으면 zapret 기능만 비활성, 나머지 엔진은 정상 동작.
132+
133+
> **참고:** VPN(IP 변경)과는 별개. zapret은 ISP 차단 우회 용도이며, Abema 같은 지역 제한(일본 IP 필요)은 VPN으로 별도 처리해야 한다.
134+
135+
### VPN 통합
136+
137+
다운로드 프로세스만 VPN을 통과시키고 웹 서버(UI)는 기존 네트워크를 그대로 사용한다.
138+
**SOCKS5 프록시 경유** 방식으로 격리한다.
139+
140+
```
141+
WireGuard / OpenVPN 터널
142+
143+
microsocks (127.0.0.1:1080, SOCKS5)
144+
145+
yt-dlp --proxy socks5://127.0.0.1:1080 ...
146+
streamlink --http-proxy socks5://127.0.0.1:1080 ...
147+
ffmpeg -http_proxy socks5://127.0.0.1:1080 ...
148+
```
149+
150+
웹 UI(Node.js)는 프록시를 사용하지 않으므로 VPN과 완전히 분리된다.
151+
152+
**설정 파일 자동 감지:**
153+
154+
| 프로토콜 | 파일 | 판별 키워드 |
155+
|---|---|---|
156+
| WireGuard | `.conf` | `[Interface]` + `[Peer]` |
157+
| OpenVPN | `.ovpn` | `dev tun` / `client` |
158+
159+
사용자가 VPN 제공업체에서 받은 설정 파일을 업로드하면 형식을 자동 감지하여 적절한 바이너리로 처리한다.
160+
161+
**주요 제공업체 지원 현황:**
162+
163+
| 제공업체 | WireGuard | OpenVPN | 설정 파일 위치 |
164+
|---|---|---|---|
165+
| Surfshark ||| 대시보드 → VPN → Manual Setup |
166+
| Mullvad ||| 계정 페이지 → WireGuard config |
167+
| ProtonVPN ||| 계정 페이지 → Downloads |
168+
| NordVPN ||| 서버 도구 페이지 |
169+
170+
**번들 바이너리:**
171+
- `wg-quick` — WireGuard 인터페이스 관리 (Linux 커널 5.6+ 내장, 유저스페이스 툴만 번들)
172+
- `openvpn` — OpenVPN 클라이언트
173+
- `microsocks` — 경량 SOCKS5 프록시 서버
174+
175+
**권한:** WireGuard/OpenVPN 모두 네트워크 인터페이스 생성에 `CAP_NET_ADMIN` 필요. zapret과 동일 권한이므로 추가 설정 불필요.
176+
177+
> **참고:** VPN은 IP 변경(지역 제한 우회)용. SNI 차단 우회는 zapret이 담당하며 둘은 독립적으로 동작한다. 필요 시 zapret + VPN 동시 사용 가능.
178+
179+
### 바이너리 번들링
180+
181+
yt-dlp / streamlink는 공식 GitHub Releases에서 플랫폼별 standalone 실행파일을 배포한다.
182+
모듈 첫 로드 시 `bin-manager.ts`가 자동으로 다운로드하여 `bin/`에 저장한다.
50183

51184
```
52185
modules/downloader/
53-
module.json
186+
bin/ ← .gitignore 처리, 런타임에 자동 채워짐
187+
yt-dlp ← GitHub Releases 자동 다운로드
188+
streamlink ← GitHub Releases 자동 다운로드
189+
ffmpeg ← ffmpeg-static npm 패키지 경로 참조
190+
zapret ← GitHub Releases 자동 다운로드 (CAP_NET_ADMIN 필요)
191+
wg-quick ← WireGuard 유저스페이스 툴
192+
openvpn ← OpenVPN 클라이언트
193+
microsocks ← SOCKS5 프록시 서버 (VPN ↔ 다운로드 엔진 중계)
54194
backend/
55-
index.ts ← createRouter() — API 라우트 등록
56-
queue.ts ← 다운로드 큐 관리 + node-cron 예약 스케줄러
57-
runner.ts ← streamlink / yt-dlp 프로세스 스폰 + stdout 파싱
58-
storage.ts ← 저장 경로 검증 + 파일 시스템 헬퍼
195+
index.ts
196+
bin-manager.ts ← 플랫폼 감지 → 바이너리 유무 확인 → 자동 다운로드
197+
runner.ts ← URL → 엔진 라우팅 → spawn + stdout 파싱
198+
queue.ts ← 큐 관리 + node-cron 예약 스케줄러
199+
storage.ts ← 저장 경로 검증 + 파일 시스템 헬퍼
200+
extractors/ ← 커스텀 TS 추출기
201+
package.json ← ffmpeg-static, puppeteer 의존성
59202
```
60203

61-
**런타임 의존성 (서버에 설치 필요):**
62-
- `streamlink` — 라이브 스트림 캡처
63-
- `yt-dlp` — 일반 영상 다운로드 (YouTube, Twitch VOD 등)
64-
- 설치 여부는 모듈 초기화 시 `which streamlink` / `which yt-dlp`로 감지, UI에 표시
65-
66204
### API 설계 (안)
67205

68206
| 메서드 | 경로 | 설명 |
69207
|---|---|---|
70-
| `GET` | `/api/downloader/status` | streamlink/yt-dlp 설치 여부 확인 |
208+
| `GET` | `/api/downloader/status` | 바이너리 준비 상태 확인 (버전 포함) |
71209
| `POST` | `/api/downloader/add` | 다운로드 추가 (URL, 저장 경로, 예약 시각) |
72210
| `GET` | `/api/downloader/queue` | 큐 + 이력 목록 조회 |
73211
| `GET` | `/api/downloader/:id/progress` | SSE 실시간 진행률 |
@@ -84,8 +222,9 @@ modules/downloader/
84222
CREATE TABLE downloader_jobs (
85223
id UUID PRIMARY KEY,
86224
url TEXT NOT NULL,
87-
title TEXT, -- yt-dlp로 미리 조회한 제목
225+
title TEXT, -- 추출기가 미리 조회한 제목
88226
type TEXT NOT NULL, -- 'vod' | 'live' | 'batch'
227+
engine TEXT, -- 'ytdlp' | 'streamlink' | 'ffmpeg' | 'extractor'
89228
status TEXT NOT NULL, -- 'pending' | 'running' | 'done' | 'failed' | 'cancelled'
90229
save_path TEXT NOT NULL,
91230
file_size BIGINT,
@@ -102,8 +241,12 @@ CREATE TABLE downloader_jobs (
102241

103242
## 미결 사항
104243

105-
- **hitomi_downloader 통합**: Python 기반이라 별도 환경 필요. yt-dlp로 커버 안 되는 사이트 대상으로 검토.
244+
- **커스텀 추출기 우선순위**: yt-dlp가 이미 지원하는 사이트는 중복 구현하지 않고 yt-dlp에 위임.
245+
커스텀 추출기는 yt-dlp/streamlink 미지원 사이트에만 작성.
246+
- **쿠키 관리**: 사이트별 로그인 세션 쿠키를 모듈 설정에서 관리 (Abema, onsen.ag 등 로그인 필요 사이트 대응).
247+
브라우저에서 쿠키를 내보내거나(`--cookies-from-browser`) Netscape 형식 파일로 업로드하는 방식 검토.
248+
- **capture 엔진 제약**: 실시간 재생 속도 캡처만 가능 (2시간 라이브 = 2시간 소요). Linux 서버는 `xvfb` 가상 디스플레이 필요.
249+
- **바이너리 업데이트**: 번들된 yt-dlp/streamlink 버전 업데이트 주기 및 자동 업데이트 여부.
106250
- **동시 다운로드 수 제한**: 서버 리소스 보호를 위한 최대 동시 작업 수 설정.
107251
- **알림**: 다운로드 완료/실패 시 알림 (Phase 3.x SMTP 또는 웹훅 연동).
108-
- **스트림 품질 선택**: Streamlink 스트림 품질(`best`, `720p` 등) 옵션 UI.
109-
- **ffmpeg 의존**: 일부 포맷 병합에 ffmpeg 필요 — 설치 감지 항목에 추가.
252+
- **스트림 품질 선택**: 엔진별 품질 옵션 UI (yt-dlp `-f`, streamlink `best/720p` 등).

0 commit comments

Comments
 (0)