[{"data":1,"prerenderedAt":18},["ShallowReactive",2],{"$fs6zmEsnQFNzNbEm9VpTd3Tx4Bzzcu3I0G86e5hBpwPU":3},{"uuid":4,"title":5,"summary":6,"content":7,"createdAt":8,"tags":9},"435b4f2f-55ac-4ebf-a2ca-5ef16c62d5be","브라우저에서 \u003Cvideo> 태그와 \u003Ccanvas>를 이용해 특정 시점의 프레임을 이미지로 추출하는 방법","브라우저 환경에서 HTML5 \u003Cvideo> 태그와 \u003Ccanvas> API를 활용하여 영상의 특정 시간대 프레임을 정확하게 추출하고, 이를 이미지 파일로 변환하는 방법을 안내합니다.","브라우저에서 영상의 특정 시점 프레임을 이미지로 추출하려면, HTML의 `\u003Cvideo>` 태그로 영상을 로드하고, JavaScript를 이용해 원하는 시간으로 이동시킨 뒤, 해당 프레임을 `\u003Ccanvas>` 요소에 그린 다음, `\u003Ccanvas>`의 내용을 이미지 데이터로 변환하여 저장할 수 있습니다. 이 가이드에서는 이러한 과정을 단계별로 설명합니다.우선, 사용자가 파일을 선택하고 재생할 수 있는 기본적인 HTML 구조를 준비합니다.```html\u003C!DOCTYPE html>\u003Chtml lang=\"ko\">\u003Chead>    \u003Cmeta charset=\"UTF-8\">    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">    \u003Ctitle>Video Frame Extractor\u003C\u002Ftitle>    \u003Cstyle>        body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; margin: 20px; }        video, canvas { border: 1px solid #ccc; max-width: 800px; margin-top: 20px; }        input[type=\"file\"] { margin-bottom: 10px; }        .controls { margin-top: 10px; display: flex; gap: 10px; align-items: center; }    \u003C\u002Fstyle>\u003C\u002Fhead>\u003Cbody>    \u003Ch1>영상 프레임 추출기\u003C\u002Fh1>    \u003Cinput type=\"file\" id=\"videoFileInput\" accept=\"video\u002F*\">    \u003Cvideo id=\"videoPlayer\" controls muted>\u003C\u002Fvideo>    \u003Cdiv class=\"controls\">        \u003Cinput type=\"range\" id=\"timeSlider\" min=\"0\" value=\"0\" step=\"0.01\">        \u003Cspan id=\"currentTimeDisplay\">00:00\u003C\u002Fspan>        \u003Cbutton id=\"captureButton\">프레임 추출\u003C\u002Fbutton>    \u003C\u002Fdiv>    \u003Ccanvas id=\"frameCanvas\">\u003C\u002Fcanvas>    \u003Ca id=\"downloadLink\" download=\"extracted-frame.png\" style=\"display: none; margin-top: 10px;\">다운로드\u003C\u002Fa>    \u003Cscript src=\"script.js\">\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>```## 1. 영상 로드 및 기본 설정`\u003Cvideo>` 태그에 `src`를 설정하여 영상을 로드합니다. 로컬 파일의 경우 `URL.createObjectURL`을 사용하여 임시 URL을 생성할 수 있습니다. 영상의 메타데이터(길이, 해상도 등)가 로드되면 `loadedmetadata` 이벤트를 통해 정보를 얻어 시간 슬라이더의 `max` 값을 설정합니다.```ts\u002F\u002F script.jsconst videoFileInput = document.getElementById('videoFileInput');const videoPlayer = document.getElementById('videoPlayer');const timeSlider = document.getElementById('timeSlider');const currentTimeDisplay = document.getElementById('currentTimeDisplay');const captureButton = document.getElementById('captureButton');const frameCanvas = document.getElementById('frameCanvas');const downloadLink = document.getElementById('downloadLink');const canvasContext = frameCanvas.getContext('2d');let videoDuration = 0;videoFileInput.addEventListener('change', (event) => {    const file = event.target.files[0];    if (file) {        const videoURL = URL.createObjectURL(file);        videoPlayer.src = videoURL;        \u002F\u002F 비디오 로드가 완료되면 URL 해제        videoPlayer.onloadedmetadata = () => {            videoDuration = videoPlayer.duration;            timeSlider.max = videoDuration.toString(); \u002F\u002F 슬라이더 최대값을 영상 길이로 설정            updateCurrentTimeDisplay();            \u002F\u002F 캔버스 크기를 비디오와 동일하게 설정            frameCanvas.width = videoPlayer.videoWidth;            frameCanvas.height = videoPlayer.videoHeight;            URL.revokeObjectURL(videoURL); \u002F\u002F 사용 후 URL 해제 (메모리 관리)        };    }});videoPlayer.addEventListener('timeupdate', updateCurrentTimeDisplay);timeSlider.addEventListener('input', () => {    videoPlayer.currentTime = parseFloat(timeSlider.value);    updateCurrentTimeDisplay();});function updateCurrentTimeDisplay() {    const minutes = Math.floor(videoPlayer.currentTime \u002F 60).toString().padStart(2, '0');    const seconds = Math.floor(videoPlayer.currentTime % 60).toString().padStart(2, '0');    const milliseconds = Math.floor((videoPlayer.currentTime % 1) * 100).toString().padStart(2, '0');    currentTimeDisplay.textContent = `${minutes}:${seconds}.${milliseconds}`;    timeSlider.value = videoPlayer.currentTime.toString(); \u002F\u002F 슬라이더 위치 업데이트}```## 2. 특정 시점 프레임 추출 및 캔버스에 그리기사용자가 프레임 추출 버튼을 클릭하면, `videoPlayer.currentTime`을 설정하여 원하는 시점으로 영상을 이동시킵니다. 이때 중요한 것은 `seeked` 이벤트입니다. `seeked` 이벤트는 영상이 특정 시간으로 이동하여 해당 프레임을 표시할 준비가 완료되었을 때 발생합니다. 이 이벤트 리스너 안에서 `canvasContext.drawImage()` 메서드를 사용하여 현재 영상 프레임을 캔버스에 그립니다.```ts\u002F\u002F script.js (이어서)captureButton.addEventListener('click', () => {    \u002F\u002F 현재 비디오의 시간을 슬라이더 값으로 설정    const targetTime = parseFloat(timeSlider.value);    videoPlayer.currentTime = targetTime;    \u002F\u002F seeked 이벤트는 비디오가 특정 시간으로 이동하여 프레임이 준비되었을 때 발생합니다.    videoPlayer.addEventListener('seeked', drawFrameToCanvas, { once: true });});function drawFrameToCanvas() {    if (!canvasContext || !videoPlayer) {        console.error('Canvas context or video player not available.');        return;    }    \u002F\u002F 캔버스 크기를 비디오와 동일하게 설정 (메타데이터 로드 시 이미 설정했겠지만, 혹시 모를 경우를 대비)    frameCanvas.width = videoPlayer.videoWidth;    frameCanvas.height = videoPlayer.videoHeight;    \u002F\u002F 비디오의 현재 프레임을 캔버스에 그립니다.    canvasContext.drawImage(videoPlayer, 0, 0, frameCanvas.width, frameCanvas.height);    \u002F\u002F 추출된 이미지를 다운로드할 수 있도록 준비    prepareDownload();    console.log(`프레임 추출 완료: ${videoPlayer.currentTime}초`);}```## 3. 추출된 이미지를 다운로드하기캔버스에 그려진 프레임을 이미지 파일로 변환하려면 `canvas.toDataURL()` 또는 `canvas.toBlob()` 메서드를 사용합니다. `toDataURL()`은 Base64 인코딩된 이미지 데이터를 반환하며, 이를 `\u003Ca>` 태그의 `href` 속성에 할당하고 `download` 속성을 함께 사용하여 사용자에게 다운로드 링크를 제공할 수 있습니다. `toBlob()`은 더 유연하며 큰 이미지에 효율적이지만, 비동기 처리가 필요합니다. 여기서는 간단한 `toDataURL` 방식을 사용합니다.```ts\u002F\u002F script.js (이어서)function prepareDownload() {    if (!frameCanvas) return;    \u002F\u002F 캔버스 내용을 PNG 이미지의 Data URL로 변환    const imageDataURL = frameCanvas.toDataURL('image\u002Fpng');    downloadLink.href = imageDataURL;    downloadLink.style.display = 'block'; \u002F\u002F 다운로드 링크 표시}```## 흔히 하는 실수 및 주의사항*   **CORS (Cross-Origin Resource Sharing) 문제**: 로컬 파일이 아닌 다른 도메인의 영상을 로드하는 경우, 보안상의 이유로 `canvas.toDataURL()` 또는 `toBlob()` 사용 시 `tainted canvas` 오류가 발생할 수 있습니다. 이 문제를 해결하려면 영상 서버에서 CORS 헤더를 올바르게 설정하거나, 영상이 동일 출처(Same-Origin)에 있어야 합니다. `video` 태그에 `crossOrigin=\"anonymous\"` 속성을 추가하는 것도 한 방법이지만, 서버 설정이 필수적입니다. 이 글은 클라이언트 측 기술 가이드이므로 서버 설정에 대한 자세한 내용은 다루지 않습니다. (참고: `HDomi\u002Fframe-pick` 프로젝트는 로컬 파일 처리를 전제로 합니다.)*   **`seeked` 이벤트의 중요성**: `videoPlayer.currentTime = targetTime;` 직후에 바로 `drawImage`를 호출하면, 영상이 아직 해당 프레임으로 완전히 이동하지 않았을 수 있습니다. 이 경우 이전 프레임이나 검은 화면이 그려질 수 있으므로, 반드시 `seeked` 이벤트를 기다린 후에 `drawImage`를 호출해야 합니다.*   **성능 및 메모리**: 매우 큰 해상도의 영상이나 빈번한 프레임 추출 작업은 브라우저의 메모리 사용량과 성능에 영향을 줄 수 있습니다. 특히 `toDataURL`은 Base64 문자열을 생성하므로 큰 이미지의 경우 `toBlob`을 사용하여 Blob 객체를 직접 다루는 것이 효율적일 수 있습니다. 환경에 따라 성능 차이가 있을 수 있으므로 실제 사용 환경에서 테스트하는 것이 중요합니다.*   **비디오 코덱 지원**: 브라우저마다 지원하는 비디오 코덱이 다를 수 있습니다. MP4 (H.264), WebM (VP8\u002FVP9) 등 널리 지원되는 포맷을 사용하는 것이 좋습니다.","2026-08-17T14:00:46.160+09:00",[10,11,12,13,14,15,16,17],"Canvas","HTML","JavaScript","Video","기술 가이드","브라우저 API","이미지 변환","프레임 추출",1788152655818]