js利用canvas根据视频地址获取视频任意一帧

766 阅读3分钟

需要根据视频地址获取视频任意一帧,就是给一个视频URL,获取任意一帧图,不需要在页面播放视频。利用canvas实现。大概思路

  1. js 创建一个视频元素
  2. 设置视频的currentTime属性,当前播放时间点,就是你想要的那一帧在视频时长的位置,用时间秒表示
  3. 利用CanvasRenderingContext2D.drawImage()把带了设置currentTime的视频传进去,就绘制了你想要的视频那一帧
  4. 利用HTMLCanvasElement.toDataURL()返回一个图片格式的url,就是你想要的视频那一帧url了

不复杂哈,有一点要注意的是,上面第2步,你想要的那一帧在视频时长的位置,用时间秒表示

这个需要知道视频帧率,frame rate,假如说你想要视频第45帧,而视频帧率是29.7,可以通过45/29.7 表示45帧在视频时长的位置。

根据视频URL获取视频任意帧
1. 创建视频元素
 const video = document.createElement('video')
 video.crossOrigin = 'anonymous' // Enable CORS
 video.src = videoUrl

设置了允许视频跨域video.crossOrigin = 'anonymous',不然后面操作会报错DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported

2.设置视频的currentTime属性
  video.addEventListener('loadedmetadata', function () {
    const frameRate = 29.7 // Assuming a frame rate of 29.7fps
    const targetFrame = 45
    const timeToSeek = targetFrame / frameRate
    video.currentTime = timeToSeek
    // video.currentTime = 0 第一帧
  })
3.利用CanvasRenderingContext2D.drawImage() 绘制视频那一帧
 video.addEventListener('seeked', function () {
    const canvas = document.getElementById('canvas')
    const context = canvas.getContext('2d')
    canvas.width = video.videoWidth
    canvas.height = video.videoHeight
    context.drawImage(video, 100, 100, canvas.width, canvas.height)
 })
4. canvas.toDataURL('image/png')返回url
 try {
      const dataURL = canvas.toDataURL('image/png')
      console.log('dataUrl', dataURL)
      document.getElementById('anyFrame').src = dataURL
    } catch (e) {
      console.error('Error exporting canvas as data URL:', e)
      alert('Failed to export the canvas.')
    }

    // Remove the video element from memory
    video.remove()

注意:页面需要创建一个canvas元素,样式为隐藏

完整代码 一个html + js示例,可以直接跑起来

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Render Any Frame of Video from URL</title>
  </head>
  <body>
    <div style="display: flex; align-items: center">
      <div>
        <div>请输入视频地址</div>
        <input
          type="text"
          id="videoUrl"
          placeholder="Enter video URL"
          value="https://www.apple.com/105/media/us/ipad-pro/2024/97d7edf3-aac0-443d-9731-38d40ff50951/anim/m4-chip/large_2x.mp4"
        />
      </div>
      <div style="margin: 10px">
        <div>请输入视频想要的那一帧</div>
        <input
          type="number"
          id="videoFrame"
          placeholder="请输入视频想要的那一帧"
          value="45"
        />
      </div>
      <button onclick="renderAnyFrame()">获取帧图</button>
    </div>
    <br />
    <canvas id="canvas" style="display: none"></canvas>
    <img
      id="anyFrame"
      alt="Any Frame of Video"
      style="width: 864px; height: 540px"
    />

    <script>
      async function renderAnyFrame(frameRate = 30) {
        const videoUrl = document.getElementById('videoUrl').value.trim()
        if (!videoUrl) {
          alert('Please enter a valid video URL')
          return
        }

        const targetFrame = Number(
          document.getElementById('videoFrame').value.trim()
        )

        try {
          const video = document.createElement('video')
          video.crossOrigin = 'anonymous' // Enable CORS
          video.src = videoUrl
          video.preload = 'auto' // Preload the video

          video.addEventListener('loadedmetadata', function () {
            const timeToSeek = targetFrame / frameRate
            video.currentTime = timeToSeek
            //video.currentTime = 0
          })

          video.addEventListener('seeked', function () {
            const canvas = document.getElementById('canvas')
            const context = canvas.getContext('2d')
            canvas.width = video.videoWidth
            canvas.height = video.videoHeight
            context.drawImage(video, 10, 10, canvas.width, canvas.height)

            try {
              const dataURL = canvas.toDataURL('image/png')
              console.log('dataUrl', dataURL)
              document.getElementById('anyFrame').src = dataURL
            } catch (e) {
              console.error('Error exporting canvas as data URL:', e)
              alert('Failed to export the canvas.')
            }

            // Remove the video element from memory
            video.remove()
          })

          video.addEventListener('error', function () {
            alert(
              'Error loading video. Make sure the video URL is correct and supports CORS.'
            )
          })

          // Load the video
          video.load()
        } catch (error) {
          console.error('Error processing video:', error)
        }
      }
      renderAnyFrame()
    </script>
  </body>
</html>

获取第100帧图 image.png 获取第170帧图 image.png

封装一个方法renderAnyFrame,方便调用,参数就传视频地址帧率要截取的帧
function renderAnyFrame(videoUrl, frameRate = 30, targetFrame = 0) {
  // videoUrl 视频地址  targetFrame 要截取的帧
  return new Promise((resolve, reject) => {
    if (!videoUrl) {
      alert('Please enter a valid video URL')
      reject('Invalid video URL')
      return
    }

    const video = document.createElement('video')
    video.crossOrigin = 'anonymous' // Enable CORS
    video.src = videoUrl
    video.preload = 'auto' // Preload the video

    video.addEventListener('loadedmetadata', function () {
      const timeToSeek = targetFrame / frameRate
      video.currentTime = timeToSeek
    })

    video.addEventListener('seeked', function () {
      const canvas = document.getElementById('canvas')
      const context = canvas.getContext('2d')
      canvas.width = video.videoWidth
      canvas.height = video.videoHeight
      context.drawImage(video, 0, 0, canvas.width, canvas.height)

      try {
        const frameSrc = canvas.toDataURL('image/png')
        console.log('frameSrc', frameSrc)
        document.getElementById('anyFrame').src = frameSrc

        // Clean up video element from DOM
        video.remove()
        resolve(frameSrc)
      } catch (e) {
        console.error('Error exporting canvas as data URL:', e)
        reject(e)
      }
    })

    video.addEventListener('error', function () {
      alert(
        'Error loading video. Make sure the video URL is correct and supports CORS.'
      )
      reject('Error loading video')
    })

    video.load() // Load the video
  })
}

const videoFile =
  'https://www.apple.com/105/media/us/ipad-pro/2024/97d7edf3-aac0-443d-9731-38d40ff50951/anim/m4-chip/large_2x.mp4'

onMounted(() => {
  // 调用获取帧图片地址
  renderAnyFrame(videoFile, 30, 10)
    .then(frameSrc => {
      console.log('Frame source:', frameSrc)
    })
    .catch(error => {
      console.error('Error:', error)
    })
})
上传视频获取视频第一帧
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Extract First Frame of Video</title>
  </head>
  <body>
    <input type="file" id="videoInput" accept="video/*" />
    <br />
    <canvas id="canvas" style="display: none"></canvas>
    <img id="firstFrame" alt="First Frame of Video" />

    <script>
      document
        .getElementById('videoInput')
        .addEventListener('change', function (event) {
          const file = event.target.files[0]
          console.log('file---videoUrl', file)
          if (file) {
            const url = URL.createObjectURL(file)
            console.log('url----', url)
            // 打印出来的 url blob:http://127.0.0.1:5500/2384e810-2cf2-4d8c-96d7-394f78f724d8
            extractFirstFrame(url)
          }
        })

      function extractFirstFrame(
        videoUrl = ''
      ) {
        const video = document.createElement('video')
        console.log('videoUrl', videoUrl)
        video.src = videoUrl
        video.addEventListener('loadeddata', function () {
          video.currentTime = 0
        })

        video.addEventListener('seeked', function () {
          const canvas = document.getElementById('canvas')
          const context = canvas.getContext('2d')
          canvas.width = video.videoWidth
          canvas.height = video.videoHeight
          context.drawImage(video, 0, 0, canvas.width, canvas.height)

          const dataURL = canvas.toDataURL('image/png')
          console.log('dataURL', dataURL)
          document.getElementById('firstFrame').src = dataURL
          URL.revokeObjectURL(videoUrl) // Clean up the URL object
        })

        // Handle error loading video
        video.addEventListener('error', function () {
          console.error('Error loading video')
          URL.revokeObjectURL(videoUrl) // Clean up the URL object
        })
      }
    </script>
  </body>
</html>

参考文档:
currentTime
CanvasRenderingContext2D.drawImage()
HTMLCanvasElement.toDataURL()