웹 투명 비디오는 alpha 를 누가 들고 오느냐로 갈린다. 1 코덱이 싣는다: video 에 source 두 개를 두고 HEVC .mov 를 먼저, VP9 .webm 을 다음에 둔다. Safari 는 VP9 의 alpha 를 못 써서 HEVC 가 먼저 와야 한다. JS 없이 되지만 HEVC alpha 인코딩은 macOS 에서만 되고, 예제 기준 1.1MB 와 3.4MB 로 무겁다. Safari 27 베타에 WebM alpha 가 들어오는 중이다(2026-08). 2 animated AVIF 를 img 로: AV1 스트림 두 개(색, 밝기로 된 alpha)라 504kB 로 작지만 Safari 에서 깨지고 60fps 도 버거우며, img 라 재생 제어·소리·source 대체가 없어 아직 못 쓴다. 3 직접 싣는다: 위는 색, 아래는 alpha 를 밝기로 쌓은 일반 영상 하나를 WebGL 이 rgb 는 위에서, alpha 는 아래 밝기에서 가져와 합친다. 예제 기준 460kB 이고 alpha 쪽은 8kB 만 더했다. ffmpeg 의 alphaextract 와 vstack 으로 만든다. seeThru 는 같은 발상을 2012 년에 canvas 2D 로 한 것이다. 가장 작지만 JS 와 WebGL 이 필요하다.



Footnotes

  1. 세 방식을 비교한다 — 네이티브(VP9 + HEVC)는 무겁고 버그가 있고, animated AVIF 는 Safari·성능에서 막힌다. 그래서 색과 alpha 를 위아래로 쌓은 AV1 영상 하나를 WebGL 로 합치는 <stacked-alpha-video> 를 만들었다. 인코딩 ffmpeg 명령 포함. ↩

  2. 2020년 기준 탐색기. GIF·APNG·PNG 루프를 거쳐 WebM alpha(GIF 5.8MB → 540KB)로 갔다가 Safari 에서 검은 배경을 보고, Safari 엔 HEVC alpha 를 주는 혼합안에 도착한다. HEVC 는 Mac 의 Finder·Compressor 로 만들고, Safari 버전은 UA 로 판별해 src 를 JS 로 넣는다. ↩

  3. 네이티브 방식(WebM + HEVC) 실무 가이드 — 무엇을 내보내고 어떻게 변환해 <video> 에 넣는지. 2026-08 갱신으로 Safari 27 베타에 WebM alpha 가 들어온다는 소식이 추가됐다. ↩

  4. alpha 를 만드는 쪽. 코덱별 alpha 지원(중간 코덱 ProRes 4444, 배포 코덱 VP9)과 ffmpeg 로 VP9·ProRes alpha 인코딩, 표현식으로 도형을 그려 alpha 마스크를 씌우는 법. ↩

  5. 2012년 라이브러리. alpha 를 영상 안에 따로 담아 두고 canvas 에 다시 그려 투명을 입힌다 — Jake 글의 스택 방식의 canvas 2D 판. README 가 스스로 “WebM·HEVC 네이티브가 되니 아마 필요 없다”고 적는다. ↩

#27

1 MP4 는 아톰의 계층이고 moov 안의 트랙(trak)마다 mdia 안에 hdlr 아톰이 하나 있다. hdlr 은 크기 4바이트, 타입 ‘hdlr’ 4바이트, version 1 과 flags 3, component type 4, component subtype 4 순이다. MP4 에서는 version·flags·component type 이 모두 0 이라 ‘hdlr’ 바로 8바이트 뒤가 subtype 이고, 오디오 트랙이면 ‘soun’, 비디오 트랙이면 ‘vide’ 다. 그래서 파일 바이트에 hdlr 다음 0 이 8개, 그다음 soun 이 이어진 16바이트가 있으면 오디오가 있다. MOV 는 component type 에 ‘mhlr’ 이 들어갈 수 있어 이 패턴이 맞지 않는다. 2 파일 전체를 받지 않고 Range 로 앞에서부터 66KB 를 받아 패턴을 찾는다. 있으면 오디오 있음. 없는데 응답이 요청한 만큼 꽉 찼고 다음 단계가 남았으면 범위를 1MB, 2MB, 4MB, 8MB, 16MB 로 늘려 다시 받는다. 응답이 덜 왔거나(파일 끝) 마지막 단계까지 없으면 오디오 없음. soun 은 큰 파일일수록 뒤에 있다: 13MB 파일은 7.3KB, 742MB 파일은 573KB 안에서 나왔다. 3 함정: Range 헤더는 예전에는 CORS safelist 밖이라 교차 출처 요청에 preflight 가 나가고, 서버의 Access-Control-Allow-Headers 에 Range 가 없으면 실패한다. safelist 에 들어온 건 Chrome 99, Safari iOS 16.1, Firefox 117 부터다.

  • 프런트엔드(클라이언트)에서 MP4 파일의 오디오 존재 여부를 확인하기
  • 브라우저 API 사용 시 호환성 문제 발생 (Chrome, Safari, Firefox 각각 다른 API 사용)
    • MP4 파일의 구조를 분석하는 방법으로 방향 전환
      • hdlr 아톰에서 오디오 존재 여부를 나타내는 Component subtype 필드가 soun일 경우 오디오가 존재할 것임.
      • MP4 파일의 특정 바이트를 선택적으로 요청하기 위해 Range 필드 사용.
      • 다양한 파일 크기에 따라 ’soun’의 예상 위치를 조사하고, 적절한 범위 값 설정.
  • FileReader API를 사용하여 서버에서 응답받은 바이너리 데이터를 읽고, 이를 분석해 오디오 여부 확인.
    • 오디오 정보가 발견되지 않으면 추가 데이터 요청하여 확인 범위를 증가시켜 반복.

await ffmpeg.writeFile('input.mp4', await fetchFile(file))
await ffmpeg.ffprobe(['-i', 'input.mp4', '-show_streams', '-o', 'output.txt'])

const data = await ffmpeg.readFile('output.txt')

…하지만 ffmpeg의 중요성을 깨달았다.


#383

A complete, cross-platform solution to record, convert and stream audio and video.


# $1 = frames로 사용될 파일들
extractFrames() {
  ffmpeg -i $1 -vf fps=30 output_frame_%d.png
}

# $1 = frames로 사용될 파일들
# $2 = output 파일
# Example: concatFrames ./frame_%5d.png output.webm
concatFrames() {
  ffmpeg -framerate 30 -i $1 -c:v libvpx-vp9 -pix_fmt yuva420p $2
}

# $1 = frame로 사용될 파일
# $2 = output 파일
# Example: frameToVideo frame.jpg output.mp4
frameToVideo() {
  ffmpeg -loop 1 -i $1 -c:v libx264 -t 10 -pix_fmt yuv420p $2
}

# $1 = 인코딩할 영상 video.mp4
# $2 = output 파일명
encodingVideo() {
  ffmpeg -an -i $1 -vcodec libx264 -pix_fmt yuv420p -profile:v baseline -level 3 "${$2}.mp4"
  ffmpeg -i "${$2}.mp4" -vcodec libvpx-vp9 -b:v 1M -acodec libvorbis "${$2}.webm"
}

# $1 = 자를 영상, $2 = 시작, $3 = 끝, $4 = output 파일
# Example: cutVideo input.mp4 00:00:00 00:01:00 output.mp4
cutVideo() {
  ffmpeg -i $1 -ss $2 -to $3 -c:v copy -c:a copy $4
}

#74