FlowPick이 브라우저에서 수백 개 동영상 세그먼트를 MP4로 병합하는 방법
FlowPick의 다운로드 버튼을 클릭하고 몇 분 기다리면, 완전한 MP4가 다운로드 폴더에 나타납니다.
이건 좀 직관에 반하는 일입니다. 브라우저는 다운로드 도구도, 동영상 처리 도구도 아닙니다. 전통적인 스트리밍 다운로드 솔루션은 예외 없이 로컬에 설치된 프로그램에 의존하거나, 데이터를 서버로 보내 처리한 후 다시 받아옵니다.
FlowPick은 전체 과정을 브라우저 탭 내에서 완료합니다. 서버도, 앱도, 명령줄도 없습니다. 이 글은 이것이 기술적으로 왜 가능한지, 그리고 실제로 어떻게 구현되었는지 설명합니다.
먼저 문제를 명확히 하자
HLS 동영상은 전송 시 하나의 완전한 파일이 아닙니다. 재생 목록 파일(M3U8)과 수백 개의 2-10초 세그먼트(.ts 파일)로 구성됩니다. 플레이어는 목록을 읽고, 순서대로 세그먼트를 가져와 실시간으로 이어 붙여 재생합니다.
30분짜리 1080P 동영상은 10초 세그먼트 기준으로 180개 파일입니다. DASH 포맷도 비슷하지만, 세그먼트가 .m4s이고 오디오와 비디오가 두 개의 독립된 스트림으로 전송됩니다.
이 동영상을 "다운로드"한다는 것은 무엇을 의미할까요?
- M3U8/MPD를 파싱하여 모든 세그먼트 URL 확보
- 수백 개 세그먼트를 하나씩 다운로드
- AES-128 암호화가 있다면 각 세그먼트 복호화
- 수백 개 조각을 하나의 연속된 파일로 결합
- HLS TS 세그먼트라면 MP4로 리먹싱 (플레이어 친화적 포맷)
- DASH라면 비디오 트랙과 오디오 트랙 병합
이것은 단순 파일 다운로드가 아니라 처리 파이프라인입니다. 전통적인 도구는 이 작업을 FFmpeg 명령줄이나 서버에 맡기지만, FlowPick은 전체 파이프라인을 브라우저로 가져왔습니다.
감지가 첫 단계: webRequest API
세그먼트를 처리하기 전에, FlowPick은 페이지에 어떤 동영상이 있는지 알아야 합니다.
브라우저 확장 프로그램은 webRequest API를 통해 네트워크 계층에서 모든 요청을 감시할 수 있습니다. DOM 계층이 아니라 실제 HTTP 요청 계층입니다. 현재 탭에서 발생하는 모든 네트워크 요청에 대해, FlowPick은 요청 URL과 응답 헤더를 볼 수 있습니다.
FlowPick은 Content-Type과 URL 패턴을 스캔하여 HLS/DASH/직링크 동영상/오디오/이미지 패턴에 일치하는 요청을 기록합니다. FlowPick 팝업을 열었을 때 보이는 목록이 바로 지금까지 캡처된 모든 미디어 리소스입니다.

이것은 동영상이 재생되지 않으면 FlowPick이 스트림을 볼 수 없는 이유도 설명합니다. M3U8 요청은 플레이어 초기화 시에만 발생하며, FlowPick은 이미 발생한 요청만 볼 수 있고 능동적으로 탐색할 수는 없습니다.
핵심 난제: TS 세그먼트를 MP4로 변환하기
동영상 세그먼트 다운로드가 끝나면 포맷 문제가 발생합니다.
HLS의 세그먼트는 MPEG-TS 포맷(.ts)입니다. 180개의 .ts 파일을 단순히 바이너리로 이어 붙이면 큰 .ts 파일이 됩니다. 이 파일은 일부 플레이어에서 재생되지만, 대부분의 기기, 동영상 편집 소프트웨어, 동영상 플랫폼은 MP4를 선호합니다.
TS와 MP4의 핵심 차이는 컨테이너 포맷입니다:
| MPEG-TS | MP4 | |
|---|---|---|
| 설계 목적 | 방송 전송, 패킷 손실 허용 | 파일 저장, 랜덤 액세스 |
| 각 패킷 | 188바이트 고정 크기 | 가변 길이 Box 구조 |
| 인덱스 위치 | 독립 인덱스 없음, 순차 읽기만 | moov atom, 파일 헤드에 배치 가능 |
| 랜덤 탐색 | 번거로움 | moov 인덱스로 직접 탐색 |
TS를 MP4로 변환하는 것은 본질적으로 내부의 비디오와 오디오 원시 데이터를 추출하여 MP4의 Box 구조에 다시 담는 것입니다. 이 작업을 **리먹싱(remux)**이라고 하며, 코덱을 건드리지 않습니다. 비디오와 오디오의 모든 비트가 그대로 유지되며, 단지 "껍데기"만 바뀝니다.
리먹싱은 GPU가 필요 없고 CPU 부하도 매우 적으며, 주요 시간은 파일 I/O에 소요됩니다. 이것이 FlowPick이 브라우저에서 이 작업을 할 수 있는 기술적 전제입니다. 만약 재인코딩(transcoding)이 필요했다면, 브라우저의 CPU 성능으로는 감당할 수 없었을 것입니다.
두 가지 엔진의 선택 로직
FlowPick 내부에는 두 가지 리먹싱 엔진이 있으며, 상황에 따라 자동 전환됩니다:
엔진 1: FFmpeg WASM
FFmpeg는 업계 표준 동영상 처리 명령줄 도구로, 거의 모든 포맷을 지원합니다. **WebAssembly(WASM)**는 브라우저에서 네이티브에 가까운 속도로 실행할 수 있는 바이너리 포맷입니다. FFmpeg를 WASM으로 컴파일하면 브라우저 탭 내에서 FFmpeg의 모든 기능을 호출할 수 있습니다.
FlowPick이 FFmpeg WASM을 호출할 때 실제 실행되는 명령은 대략 다음과 같습니다:
ffmpeg -f concat -safe 0 -i filelist.txt \
-c copy \
-movflags +faststart \
-bsf:a aac_adtstoasc \
output.mp4
매개변수 분석:
| 매개변수 | 역할 |
|---|---|
-f concat | 연결 디먹서로 여러 TS 세그먼트 처리 |
-c copy | 스트림 복사, 제로 재인코딩 |
-movflags +faststart | 인덱스를 파일 헤드로 이동, 스트리밍 재생 지원 |
-bsf:a aac_adtstoasc | AAC 캡슐화 포맷 변환 (TS는 ADTS, MP4는 ASC) |
-c copy가 핵심입니다. FlowPick이 순수 리먹싱만 수행하며, 품질 손실이 전혀 없고 높은 CPU 성능도 필요하지 않음을 보장합니다.
WASM의 FFmpeg는 브라우저의 JavaScript 엔진에서 실행되므로 성능 손실이 있지만, 리먹싱은 I/O 집약적 작업이지 CPU 집약적 작업이 아니므로 실제 성능은 나쁘지 않습니다. 실측 데이터 기준 (Chrome + Intel i7-13700 + 16GB RAM):
| 동영상 | 세그먼트 수 | 파일 크기 | FFmpeg WASM 리먹싱 소요 시간 |
|---|---|---|---|
| 10분 1080P | ~60 | ~200 MB | ~3초 |
| 30분 1080P | ~180 | ~600 MB | ~8초 |
| 60분 4K | ~360 | ~2.5 GB | ~30초 |
| 120분 4K | ~720 | ~5 GB | ~60초 |
이 속도는 일상적인 사용에 충분합니다.
FFmpeg WASM의 제한: WASM은 32비트 주소 공간에서 실행되며, 이론적 메모리 상한은 4GB입니다. 실제 시나리오에서 FFmpeg WASM은 모든 세그먼트를 가상 파일 시스템(WASM 메모리)에 써야 하므로, 대용량 파일 시나리오에서는 많은 메모리를 소비하고 한계를 초과하면 충돌합니다.
엔진 2: TSToMP4Muxer
이것은 FlowPick이 자체 개발한 순수 JavaScript 스트리밍 리먹서로, TS → MP4라는 단일 시나리오만 처리하지만 접근 방식이 완전히 다릅니다.
FFmpeg WASM의 모드는: 먼저 모든 세그먼트를 모은 후, 일괄 처리입니다. TSToMP4Muxer의 모드는: 다운로드하면서 처리하면서 디스크에 쓰기입니다.
구체적으로, TSToMP4Muxer는 세그먼트가 다운로드되는 동시에 실시간으로 TS 데이터 패킷(각 패킷 고정 188바이트)을 파싱하고, PES(Packetized Elementary Stream) 데이터를 추출하여 실시간으로 MP4 Box 포맷으로 변환한 후, 브라우저의 Streams API를 통해 지속적으로 디스크에 씁니다.
장점은 명확합니다:
- 메모리 사용량이 파일 크기와 무관 — 5GB 동영상 처리와 200MB 동영상 처리의 메모리 사용량이 비슷
- FFmpeg WASM 로드 불필요 (30MB+ WASM 파일), 시작 속도 향상
- 대용량 파일 자연 지원 — 메모리에 동시에 작은 데이터만 보관
대가는 커버리지가 더 좁다는 점: 단일 TS → MP4만 수행하며, DASH 오디오/비디오 병합처럼 두 입력이 필요한 작업은 처리할 수 없습니다.
FlowPick의 전환 로직: 일반 HLS는 TSToMP4Muxer 우선; DASH나 두 스트림 병합이 필요한 경우 FFmpeg WASM 사용. 파일이 2GB를 초과하면 TSToMP4Muxer를 우선 사용하거나 TS를 직접 출력하여 리먹싱을 건너뛰고 WASM 메모리 오버플로를 방지합니다.
파일을 어디에 쓸까: File System Access API
처리된 동영상 데이터를 어디에 둘까요? 전통적인 다운로드 도구에서는 문제가 되지 않지만, 브라우저 JavaScript는 기본적으로 파일 시스템에 직접 쓸 권한이 없습니다.
FlowPick은 Chrome과 Edge가 제공하는 **File System Access API(FSA)**를 사용합니다. 사용자가 저장 경로를 선택하면, FSA는 쓰기 가능한 파일 핸들을 제공하여 JavaScript가 스트리밍 방식으로 지속적으로 쓸 수 있게 합니다. 모든 데이터를 메모리에 모은 후 한 번에 쓰는 방식이 아닙니다.
즉, 5GB 4K 동영상이라도 메모리에는 현재 처리 중인 작은 데이터 블록만 동시에 존재합니다. FlowPick은 이론상 파일 크기 상한이 없습니다(제한은 디스크 공간과 WASM 메모리 상한이며, 후자는 TSToMP4Muxer로 우회).
Firefox도 지원하지만 Blob 방식을 사용하므로, 메모리 동작이 Chrome FSA와 약간 다르며 초대용량 파일 시 안정성이 약간 낮습니다.
AES-128 복호화: Web Crypto API
일부 HLS 스트림은 각 세그먼트를 암호화하며, M3U8에 다음과 같은 줄이 있습니다:
#EXT-X-KEY:METHOD=AES-128,URI="https://api.example.com/key?token=..."
FlowPick이 이 태그를 감지하면, 먼저 Key URI에 요청을 보내 키를 가져옵니다(현재 Cookie와 세션을 포함하여, 브라우저 확장 프로그램에서 보내는 요청이므로 실제 브라우저 세션 그대로입니다). 키를 얻은 후, 다운로드 완료된 각 세그먼트에 대해 브라우저 내장 Web Crypto API로 AES-128-CBC 복호화를 수행하고, 복호화된 데이터를 리먹싱 프로세스로 전달합니다.
키 획득 및 복호화 과정 전체가 브라우저 로컬에서 발생하며, 키가 FlowPick의 어떤 서버도 거치지 않습니다.
FlowPick이 처리할 수 없는 것: Widevine, PlayReady, FairPlay 등 DRM 체계. 이러한 콘텐츠 보호는 복호화 키를 전용 하드웨어나 운영체제 모듈에 바인딩하며, JavaScript로 접근할 수 없습니다. 이는 기술적 한계이지 기능적 선택이 아닙니다.
DASH: 오디오/비디오 분리 다운로드 후 병합
DASH는 HLS보다 한 단계 더 복잡합니다. 오디오와 비디오가 두 개의 독립된 스트림으로, 각각 세그먼트화되어 각각 전송됩니다. 다운로드하는 것은 완전한 동영상 세그먼트 더미가 아니라, 순수 비디오 세그먼트 더미(소리 없음)와 순수 오디오 세그먼트 더미(화면 없음)입니다.
FlowPick의 DASH 처리:
- MPD XML 파싱, 비디오 AdaptationSet과 오디오 AdaptationSet 식별
- 두 경로의 세그먼트 동시 다운로드 (병렬)
- 각각 init 세그먼트 다운로드 (코덱 파라미터 포함, 병합 시 필수)
- 두 경로 데이터를 FFmpeg WASM에 전달, 오디오/비디오 병합 실행
병합 명령은 대략:
ffmpeg \
-f concat -safe 0 -i video_list.txt \
-f concat -safe 0 -i audio_list.txt \
-c:v copy -c:a copy \
-movflags +faststart \
output.mp4
역시 -c copy, 제로 재인코딩. 주요 시간은 두 경로 세그먼트의 동시 다운로드와 최종 병합 I/O에 소요됩니다.
동시 다운로드: 세그먼트를 하나씩 할 필요 없음
수백 개 세그먼트를 직렬로 다운로드하면 속도가 단일 HTTP 요청의 지연 시간에 완전히 좌우됩니다. FlowPick은 다중 스레드 동시 다운로드를 사용하며, 기본 2개 동시(보수적 값, 서버 측 속도 제한 방지), 설정에서 4-6개로 조정 가능합니다.
여러 세그먼트가 병렬로 다운로드되며, 먼저 완료된 것을 먼저 처리하지만 파일에 쓸 때는 시퀀스 번호 순서로 재정렬하여 결합 순서가 올바르도록 합니다. FlowPick 내부는 다운로드 큐와 순차 쓰기 버퍼를 유지하여 이 세부 사항을 처리합니다.
다운로드 실패한 세그먼트는 자동으로 재시도됩니다(기본 3회). 세그먼트 단위가 작아 단일 실패 재시도 비용이 매우 적으며, 처음부터 다시 시작할 필요가 없습니다.
출력 포맷의 실제 차이
FlowPick은 두 가지 출력을 지원합니다:
MP4: 리먹싱된 표준 MP4, moov atom이 파일 헤드에 위치(faststart), 호환성 최고, 장기 보관/업로드/동영상 편집에 적합. 대가는 리먹싱 단계가 필요하며, 대용량 동영상은 수십 초 더 기다려야 함.
TS: 모든 세그먼트를 직접 바이너리 결합, CPU 오버헤드 제로, 거의 즉시 완료. 하지만 파일이 MP4보다 5-15% 크고(TS 컨테이너 자체 오버헤드), 일부 기기와 플랫폼에서 TS 지원이 MP4만 못함.
다운로드 후 Bilibili에 업로드하거나 편집 프로그램에 가져올 계획이라면 MP4를 선택하세요. 임시 보관이거나 이후 FFmpeg 명령줄로 계속 처리할 예정이라면 TS를 선택해 시간을 절약하세요.
브라우저에서 이 작업을 할 때의 대가
할 수 있는 것을 말했으니, 할 수 없는 것도 분명히 합니다:
재인코딩 없음. FlowPick은 컨테이너만 바꾸고 코덱은 건드리지 않습니다. H.265 동영상은 나와도 H.265 그대로이며, 구형 플레이어가 H.265를 지원하지 않으면 여전히 재생할 수 없습니다. FlowPick이 도와줄 수 없는 부분입니다.
FFmpeg WASM 메모리 상한. 4GB의 32비트 WASM 주소 공간에 브라우저 자체 오버헤드를 더하면 실제 사용 가능한 용량은 4GB보다 적습니다. 2GB 이상 파일은 자동 전환 전략(TSToMP4Muxer 또는 TS 직접 출력)으로 이 제한을 우회합니다.
WASM이 네이티브 FFmpeg보다 느림. WASM은 네이티브 코드가 아니므로 성능 손실이 있습니다. 60분 4K 동영상은 FFmpeg WASM으로 약 30초, 네이티브 FFmpeg는 5초 내에 완료될 수 있습니다. 일상적인 사용에는 30초도 괜찮지만, 매일 대량의 콘텐츠를 처리한다면 네이티브 FFmpeg 명령줄이 더 적합합니다.
하드웨어 가속 없음. FFmpeg 네이티브 버전은 NVENC/QSV/VideoToolbox를 호출하여 GPU 가속 인코딩/디코딩을 할 수 있지만, WASM 버전에는 이 능력이 없습니다. 다만 -c copy는 인코딩/디코딩을 수반하지 않으므로, 이 제한은 FlowPick의 사용 시나리오에서 큰 영향을 미치지 않습니다.
DRM 우회 불가. 이것은 하드 리밋이지 기술적 선택이 아닙니다. Widevine의 설계 목표 자체가 브라우저 확장 프로그램이 키에 접근하지 못하게 하는 것입니다.
전체적으로, FlowPick이 브라우저에서 HLS/DASH 다운로드를 완전히 처리할 수 있는 것은 몇 가지 핵심 기술 포인트의 조합 덕분입니다: WebAssembly로 FFmpeg를 브라우저에서 실행, -c copy로 처리 비용을 I/O만으로 압축, TSToMP4Muxer로 대용량 파일 메모리 문제 해결, File System Access API로 대용량 파일 디스크 쓰기 문제 해결, Web Crypto API로 AES-128 복호화 처리. 어느 하나 "블랙 매직"이 아니며, 모두 최신 브라우저 표준 기능의 직접적인 응용입니다.
추천 글
- M3U8/HLS 스트리밍 다운로드 방법: 완전 입문 가이드 — 원리를 이해했으면 조작은 이 글을 보세요
- 코드 공개! 3개월 만에 FlowPick을 만든 전 과정 — 이 원리들이 어떻게 만들어졌는지, 개발 스토리 버전
- DASH 스트리밍이란? MPD 파일 해설 — DASH의 오디오/비디오 분리 설계가 왜 존재하는지
- Bilibili 동영상을 로컬에 다운로드하는 방법 — Bilibili의 DASH는 위 기술의 전형적인 실제 사례
- 웹페이지 이미지 일괄 다운로드 방법 — 같은 확장 프로그램으로 이미지도 일괄 처리 가능
- FlowPick vs. Video DownloadHelper 비교 — 왜 "CoApp 불필요"가 중요한지