0%

提取视频关键帧并前端展示

文章字数:232,阅读全文大约需要1分钟

后端使用java截取视频关键帧,并组装成带进度的图片。前端用js视频内容展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.colin.tool.img;

import java.awt.*;
import java.util.List;
import java.awt.image.BufferedImage;

/**
* @author colin.cheng
* @date 2022-01-17
* @since 1.0.0
*/
public class ImageUtil {

public static ImageBuilder getComicStrip(int outWidth, int outHeight, List<BufferedImage> imageList) {
int showW = outWidth, showH = outHeight;
double outScale = outHeight / outWidth;
final ImageBuilder imageBuilder = new ImageBuilder(showW * imageList.size(), showH, Color.BLACK);
for (int i = 0; i < imageList.size(); i++) {
BufferedImage image = imageList.get(i);
final double height = image.getHeight();
final double width = image.getWidth();
double scale = height / width;
if(scale == outScale) {
imageBuilder.drawImg(image, i * showW, 0, showW, showH);
} else {
if(scale > outHeight) {
double n = height / showH;
double w = width / n;
imageBuilder.drawImg(image, (int)(showW * i + (showW - w) / 2), 0, (int)w, showH);
} else {
double n = width / showW;
double h = height / n;
imageBuilder.drawImg(image, showW * i, (int)((showH - h) / 2), showW, (int)h);
}
}
}
return imageBuilder;
}
}
  • 使用ffmpeg截取关键帧并调用上面的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.colin.ffm;

import com.colin.tool.img.ImageBuilder;
import com.colin.tool.img.ImageUtil;
import org.opencv.core.Mat;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.stream.Collectors;

/**
* @author colin.cheng
* @date 2021-11-17
* @since 1.0.0
*/
public class Test {

public static void main(String[] args) throws IOException {
String base = "F:\\test\\vid\\resource";
String name = "1.mp4";
String path = base + File.separator + name;
int frameIndexLength = 5;
final int count = VidUtil.countFrame(path);
final LinkedList<Mat> mats = new LinkedList<>();
VidUtil.getFrameMatByVideo(path, count / 5, mat -> mats.add(VidUtil.copy(mat)));
if(mats.size() > frameIndexLength) {
mats.removeLast();
}
ImageBuilder imageBuilder = ImageUtil.getComicStrip(200, 120,mats.stream().map(mat->VidUtil.mat2BufImg(mat, ".png")).collect(Collectors.toList()));
imageBuilder.writeToFile(new File(base + File.separator + "res.png"), "png");
}
}

前端

2.1 通用样式

  • 200 * 120
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.vidContent {
display: flex;
justify-content: flex-start;
}
.vidItem {
width: 200px;
overflow-x: hidden;
margin: 5px;
cursor: pointer;
}
.vidComicStrip {
height: 120px
}
.vidComicStrip img {
height: 100%;
position: relative;
top: 0px;
}
.vidTitle {
text-align: center;
font-size: 12px;
display: inherit;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.progressBar {
width: 0;
height: 21px;
background-color: rgba(30,144,254,0.5);
position: relative;
top: -25px;
}

2.2 两种展示方式

  1. 通过鼠标移动改变进度
  • comicStrip.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
var frameLength = 4;
var itemWidth = 200;

var currIndex = 0;
var vidContentSelect = ".vidContent";

function appendVidComicStrip(id, img, title) {
var vidItem = document.createElement("div");
vidItem.setAttribute("class", "vidItem");
var vidComicStrip = document.createElement("div");
vidComicStrip.setAttribute("class", "vidComicStrip");
var vidTitle = document.createElement("div");
vidTitle.setAttribute("class", "vidTitle");
vidItem.appendChild(vidComicStrip);
vidItem.appendChild(vidTitle);
var vidImg = document.createElement("img");
vidImg.src = img;
vidComicStrip.appendChild(vidImg);
var progressBar = document.createElement("div");
progressBar.setAttribute("class", "progressBar");
vidComicStrip.appendChild(progressBar);
vidTitle.innerText = title;
vidItem.onclick = function() {
clickVidComicStrip(id);
}
document.querySelector(vidContentSelect).appendChild(vidItem);
// 事件
vidImg.onmousemove = function(event) {
showVidComicStrip(event, this);
event.stopPropagation();
}
vidImg.onmouseout = function() {
stopVidComicStrip(this);
}
}

function showVidComicStrip(event, ob) {
var offset = event.pageX - ob.parentNode.offsetLeft;
var index = (offset / itemWidth) * frameLength
showFrame(ob, parseInt(index));
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = offset + "px";
}

function showFrame(ob, i) {
if(i <= frameLength && currIndex != i) {
currIndex = i;
ob.style.left = (0 - i * itemWidth) + "px";
}
}

function stopVidComicStrip(ob) {
ob.style.left = 0;
currIndex = 0;
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = "0px";
}

function clickVidComicStrip(id) {
console.log(id);
}
  1. 鼠标移入后自动展示
  • comicStripAuto.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
var frameLength = 4;
var itemWidth = 200;
var frameInterval = 1000;
//var progressBarShow = true;

var vidContentSelect = ".vidContent";
var timeOut = null;
var currentShow = null;

function appendVidComicStrip(id, img, title) {
var vidItem = document.createElement("div");
vidItem.setAttribute("class", "vidItem");
var vidComicStrip = document.createElement("div");
vidComicStrip.setAttribute("class", "vidComicStrip");
var vidTitle = document.createElement("div");
vidTitle.setAttribute("class", "vidTitle");
vidItem.appendChild(vidComicStrip);
vidItem.appendChild(vidTitle);
var vidImg = document.createElement("img");
vidImg.src = img;
vidComicStrip.appendChild(vidImg);
var progressBar = document.createElement("div");
progressBar.setAttribute("class", "progressBar");
vidComicStrip.appendChild(progressBar);
vidTitle.innerText = title;
vidItem.onclick = function() {
clickVidComicStrip(id);
}
document.querySelector(vidContentSelect).appendChild(vidItem);
// 事件
vidImg.onmouseover = function() {
showVidComicStrip(this);
}
vidImg.onmouseout = function() {
stopVidComicStrip(this);
}
}

function showVidComicStrip(ob) {
currentShow = ob;
showFrame(ob, 1);
}

function showFrame(ob, i) {
if(currentShow == ob && i <= frameLength) {
ob.style.left = (0 - i * itemWidth) + "px";
console.log(ob.parentNode.getElementsByClassName("progressBar")[0])
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = i / frameLength * itemWidth + "px";
timeOut = setTimeout(function() {
showFrame(ob, i + 1);
}, frameInterval)
} else {
timeOut = null;
}
}

function stopVidComicStrip(ob) {
ob.style.left = 0;
if(timeOut != null) {
clearTimeout(timeOut);
}
currentShow = null;
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = "0px";
}

function clickVidComicStrip(id) {
console.log(id);
}

2.3 使用

  1. 添加一个vidContent
  2. 调用appendVidComicStrip方法添加一个视频图标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="./comicStrip.css">
</head>
<body>
<div class="vidContent"></div>
<script type="text/javascript" src="./comicStrip.js"></script>
<script>
appendVidComicStrip("id", "imgUrl", "title");
</script>
</body>
</html>