【问题标题】:Upload a video, and save first frame as image JS上传视频,并将第一帧保存为图片 JS
【发布时间】:2020-11-26 06:41:00
【问题描述】:

我有一个网页,用户将在其中放置视频文件,该页面将上传视频,并根据用户提供的时间戳生成缩略图。

目前,我只是尝试根据视频的第一帧生成缩略图。

这里是我当前进度的一个快速示例:

(请使用 chrome,因为 firefox 会抱怨 https 链接,如果它自动下载图像,请见谅) https://stackblitz.com/edit/rxjs-qc8iag

import { Observable, throwError } from 'rxjs'

const VIDEO = {
  imageFromFrame(videoSrc: any): Observable<any> {
    return new Observable<any>((obs) => {
      const canvas = document.createElement('canvas')
      const video = document.createElement('video')
      const context = canvas.getContext('2d')
      const source = document.createElement('source');

      source.setAttribute('src', videoSrc);

      video.appendChild(source);
      document.body.appendChild(canvas)
      document.body.appendChild(video)



      if (!context) {
        throwError(`Couldn't retrieve context 2d`)
        obs.complete()
        return
      }

      video.load()

      video.addEventListener('loadedmetadata', function () {
        console.log('loadedmetadata')
        // Set canvas dimensions same as video dimensions
        canvas.width = video.videoWidth
        canvas.height = video.videoHeight
      })

      video.addEventListener('canplay', function () {
        console.log('canplay')
        canvas.style.display = 'inline'
        context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)

        // Convert canvas image to Base64
        const img = canvas.toDataURL("image/png")
        // Convert Base64 image to binary
        obs.next(VIDEO.dataURItoBlob(img))
        obs.complete()
      })
    })
  },

  dataURItoBlob(dataURI: string): Blob {
    // convert base64/URLEncoded data component to raw binary data held in a string
    var byteString
    if (dataURI.split(',')[0].indexOf('base64') >= 0) byteString = atob(dataURI.split(',')[1])
    else byteString = unescape(dataURI.split(',')[1])
    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
    // write the bytes of the string to a typed array
    var ia = new Uint8Array(byteString.length)
    for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i)
    }
    return new Blob([ia], { type: mimeString })
  },
}

    VIDEO.imageFromFrame('https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4?_=1').subscribe((r) => {
      var a = document.createElement('a')
      document.body.appendChild(a)
      const url = window.URL.createObjectURL(r)
      a.href = url
      a.download = 'sdf'
      a.click()
      window.URL.revokeObjectURL(url)
    })

问题是,它下载的图片是空的,不代表视频的第一帧。但应该已经加载了视频。并在画布中绘制。

我正在尝试解决它,但如果有人可以帮助我找出问题,谢谢。

【问题讨论】:

  • FWIW,大多数视频的第一帧会是黑色的或者不是很有趣 - 通常最好使用大约 1 秒的帧。
  • 你为什么使用any?为什么不能使用已定义的类型?
  • 1) => 是的,这就是为什么我们可以选择不同的帧,但现在我只想要第一个(在演示视频中第一个帧不是黑色的)2)因为它是最小的可重复性stackblitz 上的演示......它是一个草稿,知道它是否可能。当我在我的应用程序中实现它时,我会做得更好...
  • 我认为它失败的原因是因为画布被“污染”了,因为视频来自另一个来源,并且它的 CORS 政策不允许这样做。见这里:stackoverflow.com/questions/13674835/…
  • 我确实在某些时候收到了这个错误,但它通常会在发生时显示在控制台中。

标签: javascript html typescript


【解决方案1】:

对于任何寻找的人,我都做出了正确的工作(需要改进,但想法就在那里)。我使用可观察的,因为我的应用程序的流程使用可观察的,但您可以更改为承诺或其他 =>

  imageFromFrame(
    videoFile: File,
    options: { frameTimeInSeconds: number; filename?: string; fileType?: string } = {
      frameTimeInSeconds: 0.1,
    }
  ): Observable<File> {
    return new Observable<any>((obs) => {
      const canvas = document.createElement('canvas')
      const video = document.createElement('video')
      const source = document.createElement('source')
      const context = canvas.getContext('2d')
      const urlRef = URL.createObjectURL(videoFile)

      video.style.display = 'none'
      canvas.style.display = 'none'

      source.setAttribute('src', urlRef)
      video.setAttribute('crossorigin', 'anonymous')

      video.appendChild(source)
      document.body.appendChild(canvas)
      document.body.appendChild(video)

      if (!context) {
        throwError(`Couldn't retrieve context 2d`)
        obs.complete()
        return
      }

      video.currentTime = options.frameTimeInSeconds
      video.load()

      video.addEventListener('loadedmetadata', function () {
        canvas.width = video.videoWidth
        canvas.height = video.videoHeight
      })

      video.addEventListener('loadeddata', function () {
        context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)

        canvas.toBlob((blob) => {
          if (!blob) {
            return
          }
          obs.next(
            new File([blob], options.filename || FILES.getName(videoFile.name), {
              type: options.fileType || 'image/png',
            })
          )
          obs.complete()
          URL.revokeObjectURL(urlRef)

          video.remove()
          canvas.remove()
        }, options.fileType || 'image/png')
      })
    })
  },

【讨论】:

  • 你根本不需要使用Base64,你可以使用canvas.toBlob()。
猜你喜欢
  • 1970-01-01
  • 2011-04-04
  • 2017-07-23
  • 2021-05-05
  • 1970-01-01
  • 2016-11-14
  • 2012-05-25
  • 2016-05-26
  • 1970-01-01
相关资源
最近更新 更多