想必大家應該都見過類似這種字幕往下邊疊的視頻截圖
一般的做法都是自己把視頻某幾幀截圖,然後用作圖工具裁剪/覆蓋之類的方式來製作出來。但是作爲一個程序員,當然要思考一下效率更高的方式啊,於是就有了這篇博客。
功能目標:
直接本地選擇視頻,自定義字幕截取位置,自動添加字幕到第一幀的下方。
最終的頁面佈局如下:
技術要點:
1、通過 bolbURL 播放本地視頻;
2、通過 createImageBitmap 截取視頻某部分
實現:
爲了dom操作的方便,我這裏就直接用vue來做了。
// 16進制字符串轉rgb
function hex2rgb (hexStr) {
const value = parseInt(hexStr.replace('#', ''), 16)
const r = value >> 16 // 獲取17~24位
const g = value >> 8 & 0xff // 獲取9~16位
const b = value & 0xff // 獲取0~8位
return { r, g, b }
}
let $canvas
let $video
let ctx
let drawTimes = -1
let fullHeight = 0
const offscreenCvs = new OffscreenCanvas(0, 0)
const offscreenCtx = offscreenCvs.getContext('2d')
window.$vm = new Vue({
el: '#app',
data: function () {
return {
controls: true,
video: {
width: 200,
height: 200,
},
videoHeight: 0,
ccForceShow: true,
ccShow: false,
ccShowTimer: -1,
exportScale: 1,
cc: {
left: 0,
top: 0,
width: 0,
height: 0,
color: '#009688',
},
fileName: '選擇文件'
}
},
computed: {
canvasStyle () {
return {
width: `${this.video.width * this.exportScale}px`,
height: 'auto',
}
},
videoStyle () {
return {
width: `${this.video.width * this.exportScale}px`,
height: `${this.video.height * this.exportScale}px`,
}
},
ccStyle () {
return {
left: `${this.cc.left * this.exportScale}px`,
top: `${this.cc.top * this.exportScale}px`,
width: `${this.cc.width * this.exportScale}px`,
height: `${this.cc.height * this.exportScale}px`,
backgroundColor: `${this.ccColor}`,
}
},
ccColor () {
const rgb = hex2rgb(this.cc.color)
return `rgba(${rgb.r},${rgb.g},${rgb.b},0.5)`
},
},
mounted () {
$video = this.$refs.video
$canvas = this.$refs.canvas
ctx = $canvas.getContext('2d')
},
methods: {
onFileChange (e) {
const $fileVideo = this.$refs.fileVideo
const file = $fileVideo.files[0]
this.fileName = file.name
$video.src = URL.createObjectURL(file)
},
onCcChange (e) {
this.ccShow = true
clearTimeout(this.ccShowTimer)
this.ccShowTimer = setTimeout(() => {
this.ccShow = false
}, 1000)
},
onVideoMetaLoad () {
const $video = this.$refs.video
const videoWidth = Math.floor($video.videoWidth)
const videoHeight = Math.floor($video.videoHeight)
this.video = {
width: videoWidth,
height: videoHeight,
}
const ccHeight = 50
const ccLeft = 0
const ccWidth = Math.floor(videoWidth)
const ccTop = videoHeight - ccHeight - 15
$canvas.width = videoWidth
$canvas.height = videoHeight
this.cc = {
width: ccWidth,
height: ccHeight,
left: ccLeft,
top: ccTop,
color: this.cc.color,
}
drawTimes = 0
fullHeight = videoHeight
this.exportScale = 1
},
drawVideo () {
const video = this.video
const cc = {
left: Math.floor(this.cc.left),
top: Math.floor(this.cc.top),
width: Math.floor(this.cc.width),
height: Math.floor(this.cc.height),
}
if (drawTimes === -1) {
return alert('請先導入視頻')
}
if (drawTimes === 0) {
ctx.drawImage($video, 0, 0, video.width, video.height)
drawTimes++
}
else {
const newHeight = fullHeight + cc.height
offscreenCvs.width = video.width
offscreenCvs.height = fullHeight
offscreenCtx.drawImage($canvas, 0, 0, video.width, fullHeight)
$canvas.height = newHeight
ctx.drawImage(offscreenCvs, 0, 0, video.width, fullHeight)
createImageBitmap($video, cc.left, cc.top, cc.width, cc.height)
.then(res => {
ctx.drawImage(res, cc.left, fullHeight, cc.width, cc.height)
fullHeight = newHeight
drawTimes++
})
}
},
}
})