相信我们都会或多或少需要给前端返回视频或者音频的一些信息,那么今天这篇文章通过Java语言使用javacv来获取视频、音频、图片等元数据信息(分辨率、大小、帧等信息)
可以先导入javacv/javacv-platform依赖,由于依赖比较大,所以我们可以先去除部分不需要的依赖如下:
- <dependency>
- <groupId>org.bytedeco</groupId>
- <artifactId>javacv</artifactId>
- <version>1.4.4</version>
- <exclusions>
- <exclusion>
- <groupId>org.bytedeco</groupId>
- <artifactId>javacpp</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>flycapture</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>libdc1394</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>libfreenect</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>libfreenect2</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>librealsense</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>videoinput</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>opencv</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>tesseract</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>leptonica</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>flandmark</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>artoolkitplus</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.bytedeco</groupId>
- <artifactId>javacv-platform</artifactId>
- <version>1.4.4</version>
- <exclusions>
- <exclusion>
- <groupId>org.bytedeco</groupId>
- <artifactId>javacv</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>flycapture-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>libdc1394-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>libfreenect-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>libfreenect2-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>librealsense-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>videoinput-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>opencv-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>tesseract-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>leptonica-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>flandmark-platform</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bytedeco.javacpp-presets</groupId>
- <artifactId>artoolkitplus-platform</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- @Slf4j
- @Service
- public class FFmpegUtil {
-
- private static final String IMAGE_SUFFIX = "png";
-
- /**
- * 获取视频时长,单位为秒S
- * @param file 视频源
- * @return
- */
- @SuppressWarnings("resource")
- public static long videoDuration(File file) {
- long duration = 0L;
- FFmpegFrameGrabber ff = new FFmpegFrameGrabber(file);
- try {
- ff.start();
- duration = ff.getLengthInTime() / (1000 * 1000);
- ff.stop();
- } catch (java.lang.Exception e) {
- e.printStackTrace();
- }
- return duration;
- }
-
- /**
- * 获取视频帧图片
- * @param file 视频源
- * @param number 第几帧
- * @param dir 文件存放根目录
- * @param args 文件存放根目录
- * @return
- */
- @SuppressWarnings("resource")
- public static String videoImage(File file, Integer number, String dir, String args) {
- String picPath = StringUtils.EMPTY;
- FFmpegFrameGrabber ff = new FFmpegFrameGrabber(file);
- try {
- ff.start();
- int i = 0;
- int length = ff.getLengthInFrames();
- Frame frame = null;
- while (i < length) {
- frame = ff.grabFrame();
- //截取第几帧图片
- if ((i > number) && (frame.image != null)) {
- //获取生成图片的路径
- picPath = getImagePath(args);
- //执行截图并放入指定位置
- doExecuteFrame(frame, dir + File.separator + picPath);
- break;
- }
- i++;
- }
- ff.stop();
- } catch (FrameGrabber.Exception e) {
- e.printStackTrace();
- }
- return picPath;
- }
-
- /**
- * 截取缩略图
- * @param frame
- * @param targerFilePath 图片存放路径
- */
- public static void doExecuteFrame(Frame frame, String targerFilePath) {
- //截取的图片
- if (null == frame || null == frame.image) {
- return;
- }
- Java2DFrameConverter converter = new Java2DFrameConverter();
- BufferedImage srcImage = converter.getBufferedImage(frame);
- int srcImageWidth = srcImage.getWidth();
- int srcImageHeight = srcImage.getHeight();
- //对帧图片进行等比例缩放(缩略图)
- int width = 480;
- int height = (int) (((double) width / srcImageWidth) * srcImageHeight);
- BufferedImage thumbnailImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
- thumbnailImage.getGraphics().drawImage(srcImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
-
- File output = new File(targerFilePath);
- try {
- ImageIO.write(thumbnailImage, IMAGE_SUFFIX, output);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * 生成图片的相对路径
- * @param args 传入生成图片的名称、为空则用UUID命名
- * @return 例如 upload/images.png
- */
- public static String getImagePath(String args) {
- if (StringUtils.isNotEmpty(args)) {
- return args + "." + IMAGE_SUFFIX;
- }
- return getUUID() + "." + IMAGE_SUFFIX;
- }
-
- /**
- * 生成唯一的uuid
- * @return uuid
- */
- public static String getUUID(){
- return UUID.randomUUID().toString().replace("-","");
- }
-
- /**
- * 时长格式换算
- * @param duration 时长
- * @return HH:mm:ss
- */
- public static String formatDuration(Long duration) {
- String formatTime = StringUtils.EMPTY;
- double time = Double.valueOf(duration);
- if (time > -1) {
- int hour = (int) Math.floor(time / 3600);
- int minute = (int) (Math.floor(time / 60) % 60);
- int second = (int) (time % 60);
-
- if (hour < 10) {
- formatTime = "0";
- }
- formatTime += hour + ":";
-
- if (minute < 10) {
- formatTime += "0";
- }
- formatTime += minute + ":";
-
- if (second < 10) {
- formatTime += "0";
- }
- formatTime += second;
- }
- return formatTime;
- }
-
- /**
- * 获取图片大小Kb
- * @param urlPath
- * @return
- */
- public static String getImageSize(String urlPath){
- // 得到数据
- byte[] imageFromURL = getImageFromURL(urlPath);
- // 转换
- String byte2kb = bytes2kb(imageFromURL.length);
- return byte2kb;
- }
-
- /**
- * 根据图片地址获取图片信息
- *
- * @param urlPath 网络图片地址
- * @return
- */
- public static byte[] getImageFromURL(String urlPath) {
- // 字节数组
- byte[] data = null;
- // 输入流
- InputStream is = null;
- // Http连接对象
- HttpURLConnection conn = null;
- try {
- // Url对象
- URL url = new URL(urlPath);
- // 打开连接
- conn = (HttpURLConnection) url.openConnection();
- // 打开读取 写入是setDoOutput
- // conn.setDoOutput(true);
- conn.setDoInput(true);
- // 设置请求方式
- conn.setRequestMethod("GET");
- // 设置超时时间
- conn.setConnectTimeout(6000);
- // 得到访问的数据流
- is = conn.getInputStream();
- // 验证访问状态是否是200 正常
- if (conn.getResponseCode() == 200) {
- data = readInputStream(is);
- } else {
- data = null;
- }
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if (is != null) {
- // 关闭流
- is.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- // 关闭连接
- conn.disconnect();
- }
- return data;
- }
-
- /**
- * 将流转换为字节
- *
- * @param is
- * @return
- */
- public static byte[] readInputStream(InputStream is) {
- /**
- * 捕获内存缓冲区的数据,转换成字节数组
- * ByteArrayOutputStream类是在创建它的实例时,程序内部创建一个byte型别数组的缓冲区,然后利用ByteArrayOutputStream和ByteArrayInputStream的实例向数组中写入或读出byte型数据。
- * 在网络传输中我们往往要传输很多变量,我们可以利用ByteArrayOutputStream把所有的变量收集到一起,然后一次性把数据发送出去。
- */
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- // 创建字节数组 1024 = 1M
- byte[] buffer = new byte[1024];
- // 防止无限循环
- int length = -1;
- try {
- // 循环写入数据到字节数组
- while ((length = is.read(buffer)) != -1) {
- baos.write(buffer, 0, length);
- }
- // 强制刷新,扫尾工作,主要是为了,让数据流在管道的传输工程中全部传输过去,防止丢失数据
- baos.flush();
- } catch (IOException e) {
- e.printStackTrace();
- }
- // 字节流转换字节数组
- byte[] data = baos.toByteArray();
- try {
- // 关闭读取流
- is.close();
- // 关闭写入流
- baos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return data;
- }
-
- /**
- * 获取本地图片的字节数
- *
- * @param imgPath
- * @return
- */
- public static String pathSize(String imgPath) {
- File file = new File(imgPath);
- FileInputStream fis;
- int fileLen = 0;
- try {
- fis = new FileInputStream(file);
- fileLen = fis.available();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return bytes2kb(fileLen);
- }
-
- /**
- * 将获取到的字节数转换为KB,MB模式
- *
- * @param bytes
- * @return
- */
- public static String bytes2kb(long bytes) {
- BigDecimal filesize = new BigDecimal(bytes);
- // BigDecimal megabyte = new BigDecimal(1024 * 1024);
- // float returnValue = filesize.divide(megabyte, 2, BigDecimal.ROUND_UP).floatValue();
- // if (returnValue > 1)
- // return (returnValue + "MB");
- // BigDecimal kilobyte = new BigDecimal(1024);
- // returnValue = filesize.divide(kilobyte, 2, BigDecimal.ROUND_UP).floatValue();
- // return (returnValue + "KB");
- return filesize.toString();
- }
-
-
-
- public static String getImageFormat(String imagePath) throws IOException {
- // 字节数组
- byte[] data = null;
- String format = null;
- // 输入流
- InputStream is = null;
- // Http连接对象
- HttpURLConnection conn = null;
- ImageInputStream imageInputStream = null;
- try {
- // Url对象
- URL url = new URL(imagePath);
- // 打开连接
- conn = (HttpURLConnection) url.openConnection();
- // 打开读取 写入是setDoOutput
- // conn.setDoOutput(true);
- conn.setDoInput(true);
- // 设置请求方式
- conn.setRequestMethod("GET");
- // 设置超时时间
- conn.setConnectTimeout(6000);
- // 得到访问的数据流
- is = conn.getInputStream();
-
- // 验证访问状态是否是200 正常
- if (conn.getResponseCode() == 200) {
- imageInputStream = ImageIO.createImageInputStream(is);
- ImageReader imageReader = ImageIO.getImageReaders(imageInputStream).next();
- format = imageReader.getFormatName();
- } else {
- format = null;
- }
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if (is != null) {
- // 关闭流
- is.close();
- }
- if(imageInputStream != null){
- imageInputStream.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- // 关闭连接
- conn.disconnect();
- }
- return format;
- }
-
-
-
- /**
- * 将inputStream转化为file
- * @param is
- * @param file 要输出的文件目录
- */
- public static void inputStream2File(InputStream is, File file) throws IOException {
- OutputStream os = null;
- try {
- os = new FileOutputStream(file);
- int len = 0;
- byte[] buffer = new byte[8192];
-
- while ((len = is.read(buffer)) != -1) {
- os.write(buffer, 0, len);
- }
- } finally {
- os.close();
- is.close();
- }
- }
-
- /**
- * 获取时长
- */
- public static long getDuration(String filePath) throws Exception {
- FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
- ff.start();
- long duration = ff.getLengthInTime() / (1000 * 1000);
- ff.stop();
- return duration;
- }
-
-
- /**
- * 获取视频详情
- * @param file
- * @return
- */
- public static VideoInfoVO getVideoInfo(String file) {
- VideoInfoVO videoInfoVO = new VideoInfoVO();
- FFmpegFrameGrabber grabber = null;
- try {
- grabber = FFmpegFrameGrabber.createDefault(file);
- // 启动 FFmpeg
- grabber.start();
-
- // 读取视频帧数
- videoInfoVO.setLengthInFrames(grabber.getLengthInVideoFrames());
-
- // 读取视频帧率
- videoInfoVO.setFrameRate(grabber.getVideoFrameRate());
-
- // 读取视频秒数
- videoInfoVO.setDuration(grabber.getLengthInTime() / 1000000.00);
-
- // 读取视频宽度
- videoInfoVO.setWidth(grabber.getImageWidth());
-
- // 读取视频高度
- videoInfoVO.setHeight(grabber.getImageHeight());
-
-
- videoInfoVO.setAudioChannel(grabber.getAudioChannels());
-
- videoInfoVO.setVideoCode(grabber.getVideoCodecName());
-
- videoInfoVO.setAudioCode(grabber.getAudioCodecName());
- // String md5 = MD5Util.getMD5ByInputStream(new FileInputStream(file));
-
- videoInfoVO.setSampleRate(grabber.getSampleRate());
- return videoInfoVO;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- } finally {
- try {
- if (grabber != null) {
- // 此处代码非常重要,如果没有,可能造成 FFmpeg 无法关闭
- grabber.stop();
- grabber.release();
- }
- } catch (FFmpegFrameGrabber.Exception e) {
- log.error("getVideoInfo grabber.release failed 获取文件信息失败:{}", e.getMessage());
- }
- }
- }
-
-
- }
-
-
- @Getter
- @Setter
- public class VideoInfoVO {
- /**
- * 总帧数
- **/
- private int lengthInFrames;
-
- /**
- * 帧率
- **/
- private double frameRate;
-
- /**
- * 时长
- **/
- private double duration;
-
- /**
- * 视频编码
- */
- private String videoCode;
- /**
- * 音频编码
- */
- private String audioCode;
-
- private int width;
- private int height;
- private int audioChannel;
- private String md5;
- /**
- * 音频采样率
- */
- private Integer sampleRate;
- private Double fileSize;
-
- public String toJson() {
- try {
- ObjectMapper objectMapper = new ObjectMapper();
- return objectMapper.writeValueAsString(this);
- } catch (Exception e) {
- return "";
- }
- }
- }
- /**
- * inputStream转FileInputStream
- * @param inputStream
- * @return
- * @throws IOException
- */
- public static FileInputStream convertToFileInputStream(InputStream inputStream) throws IOException {
- File tempFile = File.createTempFile("temp", ".tmp");
- tempFile.deleteOnExit();
- try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
- byte[] buffer = new byte[1024];
- int bytesRead;
- while ((bytesRead = inputStream.read(buffer)) != -1) {
- outputStream.write(buffer, 0, bytesRead);
- }
- }
- return new FileInputStream(tempFile);
- }
- /**
- * 获取文件大小
- * @param inputStream
- * @return
- */
- public String getFileSize(InputStream inputStream){
- FileChannel fc = null;
- String size = "0";
- try {
- FileInputStream fis = convertToFileInputStream(inputStream);
- fc = fis.getChannel();
- BigDecimal fileSize = new BigDecimal(fc.size());
- size = String.valueOf(fileSize);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (null != fc) {
- try {
- fc.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- return size;
- }
- /**
- * 截取视频获得指定帧的图片
- *
- * @param video 源视频文件
- * @param picPath 截图存放路径
- */
- public String getVideoPic(InputStream video, String picPath) {
- FFmpegFrameGrabber ff = new FFmpegFrameGrabber(video);
- try {
- ff.start();
-
- // 截取中间帧图片(具体依实际情况而定)
- int i = 0;
- int length = ff.getLengthInFrames();
- // int middleFrame = length / 2;
- int middleFrame = 5;
- Frame frame = null;
- while (i < length) {
- frame = ff.grabFrame();
- if ((i > middleFrame) && (frame.image != null)) {
- break;
- }
- i++;
- }
-
- // 截取的帧图片
- Java2DFrameConverter converter = new Java2DFrameConverter();
- BufferedImage srcImage = converter.getBufferedImage(frame);
- int srcImageWidth = srcImage.getWidth();
- int srcImageHeight = srcImage.getHeight();
-
- // 对截图进行等比例缩放(缩略图)
- int width = 480;
- int height = (int) (((double) width / srcImageWidth) * srcImageHeight);
- BufferedImage thumbnailImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
- thumbnailImage.getGraphics().drawImage(srcImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
-
- InputStream inputStream = bufferedImageToInputStream(thumbnailImage);
- // File picFile = new File(picPath);
- // ImageIO.write(thumbnailImage, "jpg", picFile);
- // int available = inputStream.available();
- String imgUrl = ossUtils.putThumbInputStream(inputStream, picPath);
- ff.stop();
- return imgUrl;
- } catch (java.lang.Exception e) {
- e.printStackTrace();
- System.out.println(e);
- return null;
- }
- }