MyVideoUtils.java
8.4 KB
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package com.sanmang.util;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 视频工具
*/
public class MyVideoUtils {
private static final Logger logger = LoggerFactory.getLogger(MyVideoUtils.class);
/**
* 小截图的宽
*/
private static int width = 100;
/**
* 小截图的高
*/
private static int height = 56;
/**
* 小截图的间隔,秒
*/
private static int DELTA = 6;
private static String ffmpegPath;
/**
* 封面宽高
*/
private static String coverSize = "400x225";
/**
* HLS 视频每段的时长
*/
private static String segmentTime = "10";
static {
ResourceBundle bundle = PropertyResourceBundle.getBundle("video-config");
ffmpegPath = bundle.getString("ffmpeg.path");
width = Integer.parseInt(bundle.getString("snapshot.width"));
height = Integer.parseInt(bundle.getString("snapshot.height"));
DELTA = Integer.parseInt(bundle.getString("snapshot.interval"));
coverSize = bundle.getString("cover.size");
segmentTime = bundle.getString("hls.segment.time");
}
/**
* 将视频转为 HLS 格式 (m3u8)
* @param dir 放视频的路径
* @param videoFileName 视频文件名称(不包含路径)
* @throws IOException IOException
*/
public static void toHLS(String dir, String videoFileName) throws IOException {
String prefix = getFilePrefix(videoFileName);
Path path = Paths.get(dir, prefix);
Files.createDirectories(path);
logger.info("convert video to HLS format.");
ProcessBuilder builder = new ProcessBuilder(ffmpegPath, "-y",
"-i", Paths.get(dir, videoFileName).toString(),
"-codec:v", "libx264", "-codec:a", "mp3", "-map", "0",
"-f", "ssegment", "-segment_format", "mpegts",
"-segment_list", path.resolve(prefix + ".m3u8").toString(),
"-segment_time", segmentTime,
path.resolve(prefix + ".%03d.ts").toString());
builder.redirectErrorStream(true);
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"))) {
String line;
while ((line = reader.readLine()) != null) {
logger.debug(line);
}
}
}
/**
* 首帧截图。每隔6帧截图合并一张图,并生成对应的css
*/
public static Map<String, Object> snapshot(String dir, String videoFileName) throws IOException, InterruptedException {
Map<String, Object> map = new HashMap<>();
String prefix = getFilePrefix(videoFileName);
Path path = Paths.get(dir, prefix);
Files.createDirectories(path);
generateCover(dir, videoFileName, map);
generateMoreImageAndCSS(dir, videoFileName, map);
Path jsonPath = path.resolve(prefix + ".json");
Files.write(jsonPath, JSON.toJSONBytes(map));
return map;
}
/**
* 每隔 DELTA 秒截一张图,合并为一张图,同时生成对应的 css 文件
*/
private static void generateMoreImageAndCSS(String dir, String fileName, Map<String, Object> map) throws IOException, InterruptedException {
String prefix = getFilePrefix(fileName);
Path path = Paths.get(dir, prefix);
Files.createDirectories(path);
String cssFileName = prefix + ".css";
String combineImageName = prefix + ".combine.jpg";
String combineImagePath = path.resolve(combineImageName).toString();
int seconds = (Integer) map.get("seconds");
logger.info("generate images & css");
StringBuilder cssBuilder = new StringBuilder();
cssBuilder.append(".icon {display: inline-block; ")
.append("width: ").append(width).append("px; height: ").append(height).append("px; ")
.append("background-image: url(").append(combineImageName).append(");}\n");
int numIcons = (seconds - 1) / DELTA + 1;
map.put("numIcons", numIcons);
int countPerRow = 20;
BufferedImage combine = new BufferedImage(width * countPerRow,
(int) (height * Math.ceil(1.0 * numIcons / countPerRow)),
BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < numIcons; i++) {
logger.info("image: {}/{}", i + 1, numIcons);
String imgFilePath = path.resolve(prefix + "." + i + ".jpg").toString();
// 截图
ProcessBuilder builder = new ProcessBuilder(ffmpegPath, "-ss", "" + (i * DELTA),
"-i", Paths.get(dir, fileName).toString(),
"-f", "image2", "-s", width + "x" + height,
"-t", "0.001", "-y", imgFilePath);
builder.redirectErrorStream(true);
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"))) {
String line;
while ((line = reader.readLine()) != null) {
logger.debug(line);
}
}
process.waitFor(); // 保证图片已经生成才能进行后续的合并图片。会增加执行时间
// 合并到总图片中
File imageFile = new File(imgFilePath);
BufferedImage image = ImageIO.read(imageFile);
int x = width * (i % countPerRow);
int y = height * (i / countPerRow);
combine.getGraphics().drawImage(image, x, y, null);
// css
cssBuilder.append(".icon-").append(i).append("{background-position: ").append(-x).append("px ")
.append(-y).append("px;}\n");
// delete temp image
Files.delete(Paths.get(imgFilePath));
}
// 保存合并后的图片
logger.info("save combine image: {}", combineImageName);
ImageIO.write(combine, "JPG", new File(combineImagePath));
// 保存 css
logger.info("generate css: {}", cssFileName);
Files.write(path.resolve(cssFileName), cssBuilder.toString().getBytes("utf-8"));
map.put("css", cssFileName);
map.put("combine", combineImageName);
}
private static String getFilePrefix(String fileName) {
return fileName.substring(0, fileName.lastIndexOf("."));
}
/**
* 第一页截图,以及得到总时长
*/
private static void generateCover(String dir, String fileName, Map<String, Object> map) throws IOException {
String prefix = getFilePrefix(fileName);
String imageName = prefix + ".jpg";
Path path = Paths.get(dir, prefix);
Files.createDirectories(path);
String imageFullPath = path.resolve(imageName).toString();
ProcessBuilder builder = new ProcessBuilder(ffmpegPath, "-y",
"-i", Paths.get(dir, fileName).toString(),
"-f", "image2", "-s", coverSize,
"-t", "0.001", imageFullPath);
builder.redirectErrorStream(true);
logger.info("generate cover: {}", imageName);
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"))) {
String line;
Pattern pattern = Pattern.compile("\\s+Duration: (.+?),.+");
while ((line = reader.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
String timeStr = matcher.group(1);
map.put("duration", timeStr);
String[] split = timeStr.split(":|\\.");
int seconds = Integer.parseInt(split[0]) * 60 * 60
+ Integer.parseInt(split[1]) * 60
+ Integer.parseInt(split[2]);
map.put("seconds", seconds);
}
}
}
}
}