3 Star 31 Fork 13

Kay/blog-source-host

加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
index.html 8.55 KB
一键复制 编辑 原始数据 按行查看 历史
Kay 提交于 2020-07-04 15:19 +08:00 . 视频字幕截取器
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>本地视频字幕截取</title>
<style>
* {
margin: 0;
padding: 0;
}
html {
height: 100%;
background-color: #f8f8f8;
}
body {
height: 100%;
box-sizing: border-box;
padding: 30px;
}
#app {
height: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
}
#left {
margin-right: 10px;
border-right: 1px solid #ccc;
padding-right: 10px;
}
#right {
height: 100%;
overflow: auto;
color: #999;
}
#video-wrapper {
position: relative;
font-size: 0;
}
#video {
display: inline-block;
/*height: 100%;*/
margin-right: 10px;
}
#canvas {
display: inline-block;
background-color: #f0f0f0;
}
#cc {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
user-select: none;
pointer-events: none;
opacity: 0;
transition: opacity 300ms;
}
#cc.show {
opacity: 1;
}
.button {
display: inline-block;
margin-top: 10px;
font-size: 14px;
padding: 2px 8px;
border: 1px solid #8eddff;
background-color: #98e5ff;
cursor: pointer;
border-radius: 4px;
outline: none;
margin-right: 10px;
user-select: none;
}
.button:hover {
background-color: #a3edff;
}
.button:active {
background-color: #7acbff;
}
input[type=file] {
display: none;
}
input[type=checkbox] {
display: block;
width: 15px;
height: 15px;
}
input[type=color] {
display: block;
width: 40px;
height: 20px;
}
input[type=range] {
display: inline-block;
width: 600px;
vertical-align: middle;
}
dl {
margin-top: 10px;
white-space: nowrap;
}
dt {
display: inline-block;
width: 140px;
margin-right: 10px;
vertical-align: middle;
}
dd {
display: inline-block;
vertical-align: middle;
}
section {
margin-top: 20px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div id="left">
<div id="video-wrapper">
<video id="video" ref="video" :controls="controls" :style="videoStyle" @loadedmetadata="onVideoMetaLoad($event)"></video>
<div id="cc" :class="{show: ccShow||ccForceShow}" :style="ccStyle"></div>
</div>
<label id="video-picker" class="button" for="file-video">{{ fileName }}</label>
<button id="draw" class="button" @click="drawVideo">截取</button>
<section>
<dl>
<dt>{{ ccForceShow ? '隐藏' : '显示' }}字幕遮罩</dt>
<dt><input type="checkbox" v-model="ccForceShow"></dt>
</dl>
</section>
<section>
<dl>
<dt>修改遮罩颜色</dt>
<dt><input type="color" v-model="cc.color"/></dt>
</dl>
</section>
<section>
<dl>
<dt>{{ controls ? '隐藏' : '显示' }}视频控制器</dt>
<dt><input type="checkbox" v-model="controls"></dt>
</dl>
</section>
<section>
<dl>
<dt>字幕距离左侧</dt>
<dd>
<input v-model="cc.left" type="range" min="0" :max="video.width" step="1" @input="onCcChange('l')">
</dd>
</dl>
<dl>
<dt>字幕距离顶部</dt>
<dd>
<input v-model="cc.top" type="range" min="0" :max="video.height" step="1" @input="onCcChange('t')">
</dd>
</dl>
<dl>
<dt>字幕宽度</dt>
<dd>
<input v-model="cc.width" type="range" min="0" :max="video.width" step="1" @input="onCcChange('w')">
</dd>
</dl>
<dl>
<dt>字幕高度</dt>
<dd>
<input v-model="cc.height" type="range" min="0" :max="video.height" step="1" @input="onCcChange('h')">
</dd>
</dl>
<dl>
<dt>调整尺寸</dt>
<dd>
<input id="scale" v-model="exportScale" type="range" min="0" :max="1" step="0.01">
</dd>
</dl>
</section>
</div>
<div id="right">
<canvas id="canvas" ref="canvas" title="右键另存为" :style="canvasStyle"></canvas>
<p>右键另存为导出图片</p>
</div>
<input id="file-video" ref="fileVideo" type="file" accept="video/*" @change="onFileChange($event)">
</div>
<script>
// 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++
})
}
},
}
})
</script>
</body>
</html>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
其他
1
https://gitee.com/kaysama/blog-source-host.git
git@gitee.com:kaysama/blog-source-host.git
kaysama
blog-source-host
blog-source-host
master

搜索帮助