背景:
视频第一帧就是在视频中提取第一帧的图片画面,在视频处理中,截帧可以用于视频预览、封面生成、缩略图制作等多种应用场景。以下是一个简化的步骤,描述如何使用FFmpeg
来获取视频的第一帧。
1.添加依赖:
首先,你需要将FFmpeg
和相关的依赖项添加到你的项目中。如果你使用Maven,可以在pom.xml
中添加以下依赖【本来就一个依赖的,但是那个依赖太大,所以就从中过滤了一些无效依赖】
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<classifier>windows-x86_64</classifier>
<version>4.5.3-1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<classifier>linux-x86_64</classifier>
<version>4.5.3-1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<classifier>windows-x86_64</classifier>
<version>0.3.17-1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<classifier>linux-x86_64</classifier>
<version>0.3.17-1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<classifier>windows-x86_64</classifier>
<version>4.4-1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<classifier>linux-x86_64</classifier>
<version>4.4-1.5.6</version>
</dependency>
2.代码案例:
将第一帧的图片以流的形式,上传到OBS,最后返回OBS的图片url给到前端
/**
* 截取视频第一帧图片【解决图片旋转问题】
* @param videoUrl
* @return
*/
public String getFirstFrameOfVideo(String videoUrl) throws Exception {
if (StringUtils.isBlank(videoUrl)){
throw new Exception("上传视频地址为空!");
}
try {
URL url = new URL(videoUrl);
URLConnection con = url.openConnection();
if (enable) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
con = url.openConnection(proxy);
}
con.connect();
InputStream is = con.getInputStream();
FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(is);
frameGrabber.start();
int length = frameGrabber.getLengthInFrames();
log.info("============> length:{}", length);
int i = 0;
Frame f = null;
while (i < length) {
// 过滤前5帧,避免出现全黑的图片
f = frameGrabber.grabFrame();
if ((i > 5) && (f.image != null)) {
break;
}
i++;
}
log.info("============> f:{}", f == null ? "为null" : "不为null");
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bufferedImage = converter.getBufferedImage(f);
String rotate = frameGrabber.getVideoMetadata("rotate");
if (StringUtils.isNotEmpty(rotate)) {
bufferedImage = rotate(bufferedImage, Integer.parseInt(rotate));
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "jpg", os);
InputStream input = new ByteArrayInputStream(os.toByteArray());
String coverImgPath = obsPublicUpload(input, DateUtil.format(DateUtil.date(), "yyyy/MM/dd"), "videoImg-" + DateUtil.format(DateUtil.date(), "HHmmssSSS") + IdUtil.simpleUUID() + ".jpg");
frameGrabber.stop();
os.close();
is.close();
return coverImgPath;
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
public static BufferedImage rotate(BufferedImage src, int angel) {
int src_width = src.getWidth(null);
int src_height = src.getHeight(null);
int type = src.getColorModel().getTransparency();
Rectangle rect_des = calcRotatedSize(new Rectangle(new Dimension(src_width, src_height)), angel);
BufferedImage bi = new BufferedImage(rect_des.width, rect_des.height, type);
Graphics2D g2 = bi.createGraphics();
g2.translate((rect_des.width - src_width) / 2, (rect_des.height - src_height) / 2);
g2.rotate(Math.toRadians(angel), src_width / 2, src_height / 2);
g2.drawImage(src, 0, 0, null);
g2.dispose();
return bi;
}
public static Rectangle calcRotatedSize(Rectangle src, int angel) {
if (angel >= 90) {
if(angel / 90 % 2 == 1) {
int temp = src.height;
src.height = src.width;
src.width = temp;
}
angel = angel % 90;
}
double r = Math.sqrt(src.height * src.height + src.width * src.width) / 2;
double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;
double angel_alpha = (Math.PI - Math.toRadians(angel)) / 2;
double angel_dalta_width = Math.atan((double) src.height / src.width);
double angel_dalta_height = Math.atan((double) src.width / src.height);
int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_width));
int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_height));
int des_width = src.width + len_dalta_width * 2;
int des_height = src.height + len_dalta_height * 2;
return new java.awt.Rectangle(new Dimension(des_width, des_height));
}
/**
* 上传封面图片
* @param inputStream
* @param dirName
* @param fileName
* @return
*/
public String obsPublicUpload(InputStream inputStream, String dirName, String fileName){
log.info("===================> 开始将视频封面上传obs");
ObsConfiguration oc = new ObsConfiguration();
if (enable) {
oc.setHttpProxy(host, port, null, null);
}
oc.setEndPoint(obsSignUtil.getEndPoint());
String path = obsSignUtil.getPublicPrefix()+dirName+"/"+fileName;
try (ObsClient obsClient = new ObsClient(obsSignUtil.getAk(), obsSignUtil.getSk(), oc)) {
log.info("视频第一帧封面地址为:"+path);
// 声明上传请求对象
PutObjectRequest request = new PutObjectRequest();
// 设置桶名
request.setBucketName(obsSignUtil.getBucketName());
// 设置上传路径
request.setObjectKey(path);
// 上传文件
request.setInput(inputStream);
// 设置对象访问权限为公共读
request.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ);
obsClient.putObject(request);
String resultUrl = obsSignUtil.getObsPrefix() + path;
log.info("=======================> 上传obs响应url地址:{}", resultUrl);
return resultUrl;
} catch (IOException e) {
throw new RuntimeException(e);
}
}