java使用ffmpeg获取上传视频的缩略图和时长

介绍

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。这个项目最早由Fabrice Bellard发起,2004年至2015年间由Michael Niedermayer主要负责维护。许多FFmpeg的开发人员都来自MPlayer项目,而且当前FFmpeg也是放在MPlayer项目组的服务器上。项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。

官网地址

git地址

springboot使用场景

maven依赖

因为 FFmpeg 依赖做了多平台兼容,整体的包有200多MB,这边我们只取我们需要的功能
(查看依赖树会发现就像老太婆的裹脚布)

<!-- 视频处理 -->
        <!--获取视频首帧作为图片展示-->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp</artifactId>
            <version>1.5.8</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>1.5.8</version>
            <exclusions>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>javacpp</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>opencv</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>flycapture</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>libdc1394-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>libfreenect</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>libfreenect2</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>librealsense</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>librealsense2</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>artoolkitplus</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>flandmark</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>leptonica</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>tesseract</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.8</version>
            <exclusions>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>ffmpeg-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>openblas-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>opencv-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>flycapture-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>libdc1394-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>libfreenect-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>libfreenect2-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>librealsense-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>librealsense2-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>artoolkitplus-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>flandmark-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>leptonica-platform</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>tesseract-platform</artifactId>
                </exclusion>
                <exclusion>
                    <artifactId>javacpp</artifactId>
                    <groupId>org.bytedeco</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>openblas</artifactId>
                    <groupId>org.bytedeco</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>5.1.2-1.5.8</version>
            <classifier>windows-x86_64</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>5.1.2-1.5.8</version>
            <classifier>linux-x86_64</classifier>
        </dependency>

编写工具类

package com.ruoyi.common.utils.file;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 获取视频首帧图片用于界面展示
 *
 * @author zzxjson
 * 基本信息描述:https://www.cnblogs.com/liangjingfu/p/12858018.html
 * <p>
 * Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'F:\temporary\123456789.mp4':
 * Metadata:
 * major_brand     : isom
 * minor_version   : 512
 * compatible_brands: isomiso2avc1mp41
 * encoder         : Lavf58.12.100
 * description     : Tencent SID MTS
 * Duration: 00:04:13.21, start: 0.000000, bitrate: 3167 kb/s
 * Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 3033 kb/s, 24 fps, 24 tbr, 90k tbn, 48 tbc (default)
 * Metadata:
 * handler_name    : VideoHandler
 * Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
 * Metadata:
 * handler_name    : SoundHandler
 */
public class FFmpegUtil {
    /**
     * @throws
     * @Title: getTempPath
     * @Description: 生成视频的首帧图片方法
     * @author: Zing
     * @param: @param tempPath 生成首帧图片的文件地址
     * @param: @param filePath 传进来的线上文件
     * @param: @return
     * @param: @throws Exception
     * @return: boolean
     */
    public static Video getTempPath(File targetFile, File file2) throws Exception {
        Video video = new Video(false,"00:00:00:00");
        if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
        }
        System.out.println("确认文件是否是视频...");
        //判断文件是否为视频
        if (isVideo(file2.getAbsolutePath())) {
            System.out.println("确认成功!");
            //判断文件是否存在
            if (file2.getParentFile().exists()) {
                FFmpegFrameGrabber ff = new FFmpegFrameGrabber(file2);
                //获取时长
                video.setDuration(durationlFormat(ff));
                //转缩略图
                video.setGetThumbnail(getThumbnail(file2, targetFile));
                ff.close();
            } else {
                System.out.println("路径内容错误!" + file2.getParentFile());
            }
        } else {
            System.out.println("确认失败!");
        }
        return video;
    }

    private static Boolean getThumbnail(File sourceFile, File targetFile) throws FrameGrabber.Exception {
        FFmpegFrameGrabber ff = null;
        Frame frame = null;
        try {
            ff = new FFmpegFrameGrabber(sourceFile);
            ff.setVideoCodec(avcodec.AV_CODEC_ID_MJPEG);
            ff.start();
            //转缩略图
            int length = ff.getLengthInFrames();
            int i = 0;
            while (i < length) {
                // 过滤前20帧,避免出现全黑的图片()
                frame = ff.grabFrame().clone();
                if ((i > 20) && (frame.image != null)) {
                    break;
                }
                i++;
            }
            int tmpWidth = frame.imageWidth;
            int tmpHeight = frame.imageHeight;
            // 对截取的帧进行等比例缩放
            int width = 300;
            int height = (int) (((double) width / tmpWidth) * tmpHeight );
            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
            BufferedImage renderedImage = new Java2DFrameConverter().getBufferedImage(frame);
            bi.getGraphics().drawImage(renderedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
            if (ImageIO.write(bi, "png", targetFile)) {
                System.out.println("输出图片成功!");
                return true;
            } else {
                System.out.println("输出图片失败!");
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            if (ff != null) {
                try {
                    ff.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (frame != null) {
                frame.close();
            }
        }
    }




    /**
     * @throws
     * @Title: isVideo
     * @Description:判断是不是视频
     * @author: Zing
     * @param: @param path 文件路径
     * @param: @return
     * @return: boolean       true是视频 false非视频
     */
    public static boolean isVideo(String path) {
        //设置视频后缀
        List<String> typeList = new ArrayList<>();
        typeList.add("mp4");
        typeList.add("flv");
        typeList.add("avi");
        typeList.add("rmvb");
        typeList.add("rm");
        typeList.add("wmv");
        //获取文件名和后缀
        String suffix = path.substring(path.lastIndexOf(".") + 1);
        System.out.println(suffix);
        for (String type : typeList) {
            //统一为大写作比较
            if (type.toUpperCase().equals(suffix.toUpperCase())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 格式化时长
     * @return 00:00:00:00
     */
    private static String durationlFormat(FFmpegFrameGrabber ff) throws FrameGrabber.Exception {
        try{
            ff.start();
            long duration = ff.getLengthInTime() / 1000;
            ff.close();
            return String.format("%02d:%02d:%02d",
                    TimeUnit.MILLISECONDS.toHours(duration),
                    TimeUnit.MILLISECONDS.toMinutes(duration) % TimeUnit.HOURS.toMinutes(1),
                    TimeUnit.MILLISECONDS.toSeconds(duration) % TimeUnit.MINUTES.toSeconds(1));
        }catch (Exception e){
            throw e;
        }finally {
            ff.close();
        }
    }

    public static class Video{
        private Boolean getThumbnail;
        private String duration;

        public Video(Boolean getThumbnail, String duration) {
            this.getThumbnail = getThumbnail;
            this.duration = duration;
        }

        public Boolean getGetThumbnail() {
            return getThumbnail;
        }

        public void setGetThumbnail(Boolean getThumbnail) {
            this.getThumbnail = getThumbnail;
        }

        public String getDuration() {
            return duration;
        }

        public void setDuration(String duration) {
            this.duration = duration;
        }

    }
}

实际应用

public AjaxResult add(MultipartFile file){
        //临时文件位置
        String newPath = profilePath + "wareTemporary/";
        String originalFilename = file.getOriginalFilename();
        UUID uuid = UUID.randomUUID();
        //临时视频地址
        String temporaryVideoPath = newPath + uuid + originalFilename.substring(originalFilename.lastIndexOf("."));
        // 将上传的文件保存到临时文件中
        File tempFile = FileUtil.file(temporaryVideoPath);
        //临时图片地址
        String temporaryThumbnailPath = newPath + uuid + ".png";
        File thumbnail = new File(temporaryThumbnailPath);
        try{
            file.transferTo(tempFile);
            //获取课件的时长和缩略图
            FFmpegUtil.Video tempPath = FFmpegUtil.getTempPath(thumbnail,tempFile);
            if (!tempPath.getGetThumbnail()){
                return AjaxResult.error("确认文件类型出错");
            }
            //上传缩略图
            String thumbnailUrl = minioUtil.uploadFile(thumbnail);
            //上传视频
            String videoUrl = minioUtil.uploadFile(tempFile);
            return AjaxResult.success(videoUrl).put("thumbnail",thumbnailUrl).put("duration",tempPath.getDuration());
        }catch (Exception e){
            logger.error("上传文件失败",e);
            return AjaxResult.error(e.getMessage());
        }finally {
            //删除临时文件
            thumbnail.delete();
            tempFile.delete();
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泽泽泽json

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值