springboot java ffmpeg 视频压缩、提取视频帧图片、获取视频分辨率

用到的maven依赖:

lombok依赖就不贴出来了

  <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg-platform</artifactId>
            <version>4.3.2-1.5.5</version>
  </dependency>
   <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
      </dependency>
 <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.2</version>
  </dependency>

工具类:

import cn.hutool.core.io.IoUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.avcodec.AVCodec;
import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avformat;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacpp.PointerPointer;

import java.io.*;
import java.util.concurrent.TimeUnit;

@Slf4j
public class VideoUtils {

    static class LazyFfmpeg {
        private static final String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
    }

    public static String ffmpeg() {
        return LazyFfmpeg.ffmpeg;
    }

    /**
     * 压缩视频
     * @param inputFilePath 压缩前视频地址
     * @param outputFilePath 压缩后视频地址
     */
    public static void compressVideo(String inputFilePath, String outputFilePath) {

        if (StringUtils.isAnyBlank(inputFilePath, outputFilePath)) {
            throw new RuntimeException("输入视频路径或输出视频文件路径不能为空");
        }

        if (StringUtils.equals(inputFilePath, outputFilePath)) {
            throw new RuntimeException("outputFilePath不能和inputFilePath相同");
        }

        validIsFile(new File(inputFilePath));

        ProcessBuilder processBuilder = new ProcessBuilder(
                ffmpeg(),
                "-y",                     // 自动覆盖输出文件
                "-i", inputFilePath,      // 输入文件路径
                "-crf","30",
                "-c:v","h264",
                "-preset", "slow",            // 使用较慢的预设来提高压缩效率
//                "-b:v", "1000",  // 设置视频比特率为 1000 kbps
//                "-vf", String.format("scale=%s:%s", 1920, 1080),
//                "-c:a", "copy",           // 保持音频编码不变
                "-c:a", "aac",                  // 使用 AAC 音频编码
                "-b:a", "2k",                 // 设置音频比特率为 128 kbps
                outputFilePath            // 输出文件路径

        );

        StringBuilder stringBuilder = new StringBuilder();

        int exitCode;
        try {
            Process process = processBuilder.start();

            // 捕获错误输出
            processErrorMsg(process, stringBuilder);

            // 等待 FFmpeg 进程完成
            exitCode = process.waitFor();

        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        if (exitCode != 0) {
            throw new RuntimeException(stringBuilder.toString());
        }


    }

    /**
     * 提取图片
     *
     * @param videoPath 视频路径
     * @param second    提取指定时间图片
     * @param timeout   等待的最长时间
     * @param unit      参数的时间 timeout 单位
     * @return 图片
     */
    public static byte[] ffmpegExtractImage(String videoPath, Number second, long timeout, TimeUnit unit) {

        if (timeout <= 0) {
            throw new IllegalArgumentException("timeout不能小于等于0");
        }

        if (second == null) {
            second = 0;
        }

        if (unit == null) {
            unit = TimeUnit.MINUTES;
        }

        File videoFile = new File(videoPath);
        validIsFile(videoFile);
        ProcessBuilder extractBuilder = new ProcessBuilder(
                ffmpeg(),
                "-ss", second.toString(),
                "-i", videoPath,
                "-f", "image2pipe",
                "-vframes", "1",
//                "-vcodec", "png",//如果觉得照片不清晰,就启用此选项,但是照片会变大
                "-"
        );

        try {

            Process process = extractBuilder.start();

            try (InputStream inputStream = process.getInputStream()) {

                byte[] bytes = IoUtil.readBytes(inputStream);

                boolean result = process.waitFor(timeout, unit);
                if (!result) {
                    throw new RuntimeException("子进程退出之前已超过等待时间");
                }

                return bytes;
            }

        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 获取视频分辨率
     *
     * @param videoFilePath 视频路径
     */
    public static int[] getVideoResolution(String videoFilePath) {

        validIsFile(new File(videoFilePath));

        AVFormatContext formatContext = avformat.avformat_alloc_context();
        AVCodecContext codecContext = avcodec.avcodec_alloc_context3(null);

        // 打开视频文件
        if (avformat.avformat_open_input(formatContext, videoFilePath, null, null) != 0) {
            throw new RuntimeException("无法打开视频文件");
        }

        // 获取视频流信息
        if (avformat.avformat_find_stream_info(formatContext, (PointerPointer) null) < 0) {
            throw new RuntimeException("无法获取视频流信息");
        }

        // 查找视频流
        int videoStreamIndex = -1;
        for (int i = 0; i < formatContext.nb_streams(); i++) {
            if (formatContext.streams(i).codecpar().codec_type() == avutil.AVMEDIA_TYPE_VIDEO) {
                videoStreamIndex = i;
                break;
            }
        }

        if (videoStreamIndex == -1) {
            throw new RuntimeException("视频流未找到");
        }

        // 获取视频解码器上下文
        avcodec.avcodec_parameters_to_context(codecContext, formatContext.streams(videoStreamIndex).codecpar());

        // 查找解码器
        AVCodec codec = avcodec.avcodec_find_decoder(codecContext.codec_id());
        if (codec == null) {
            throw new RuntimeException("无法找到解码器");
        }

        // 打开解码器
        if (avcodec.avcodec_open2(codecContext, codec, (PointerPointer) null) < 0) {
            throw new RuntimeException("无法打开解码器");
        }

        // 获取视频分辨率
        int width = codecContext.width();
        int height = codecContext.height();

        // 清理资源
        codecContext.close();

        return new int[]{width, height};
    }
    private static void processErrorMsg(Process process, StringBuilder stringBuilder) {

        new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    stringBuilder.append(line);
                }
            } catch (IOException e) {
                log.error("打印命令行错误日志出现异常  errMsg:{}", e.getMessage(), e);
            }
        }).start();
    }

    public static void validIsFile(File file) {
        validExists(file);
        if (!file.isFile()) {
            throw new IllegalArgumentException("不是文件");
        }
    }

    public static void validExists(File file) {
        if (!file.exists()) {
            throw new IllegalArgumentException("videoPath不存在");
        }
    }
}

<think>我们正在处理用户的问题:如何在上传文件后识别文件属性并将数据录入数据库根据引用内容,我们可以提取以下关键点:引用[1]:使用JavaSpring框架,通过`file.getOriginalFilename()`获取文件名,并保存到数据库。引用[2]:在Python中,连接数据库并执行SQL插入语句,将数据(例如识别出的属性)存入数据库。引用[3]:Python读取文件数据并写入数据库,同时需要会写SQL语句。引用[4]:PHP配置实现文件上传,并保存文件信息到数据库。引用[5]:Java读取excel文件,通过FileInputStream读取文件字节流,然后导入数据库。用户的问题:上传文件后识别文件属性并存储到数据库的实现方法。分析:1.“识别文件属性”可能包括:-基本属性:文件名、文件大小、文件类型(扩展名)、上传时间等。-特定文件的内容属性:例如图片的宽高、音频的时长、文档的页数等,这需要根据具体文件类型进行解析。2.存储到数据库:需要设计数据库表,然后通过编程语言(如Java、Python、PHP)连接数据库,执行插入操作。由于用户没有指定语言,我们可以给出通用的步骤和伪代码,并举例两种常见语言(Java和Python)的实现。步骤:1.接收上传的文件:通过HTTP请求接收文件(通常在Web应用中)。2.获取基本文件属性:如文件名、大小、类型(MIME类型)等。3.(可选)解析文件以获取更多属性:根据需求,可能需要使用第三方库解析文件(如使用ApachePOI解析Excel,使用Pillow解析图片等)。4.连接数据库。5.构建SQL插入语句,将文件属性插入到数据库表中。6.执行SQL语句,并关闭数据库连接。举例实现(两种语言):1.Java(SpringBoot框架)示例:假设我们有一个实体类FileEntity,对应数据库表file_table(包含字段:id,fileName,fileSize,fileType,uploadTime,以及其他自定义属性)。使用SpringDataJPA或JDBC进行数据库操作。伪代码:```java@PostMapping("/upload")publicStringhandleFileUpload(@RequestParam("file")MultipartFilefile){//获取基本属性StringfileName=file.getOriginalFilename();longfileSize=file.getSize();StringfileType=file.getContentType();//可选:解析特定属性(以图片宽高为例,使用ImageIO)intwidth=0,height=0;if(fileType!=null&&fileType.startsWith("image/")){try(InputStreamis=file.getInputStream()){BufferedImageimage=ImageIO.read(is);if(image!=null){width=image.getWidth();height=image.getHeight();}}catch(Exceptione){e.printStackTrace();}}//连接数据库并插入(这里使用JdbcTemplate)jdbcTemplate.update("INSERTINTOfile_table(file_name,file_size,file_type,upload_time,width,height)VALUES(?,?,?,?,?,?)",fileName,fileSize,fileType,newTimestamp(System.currentTimeMillis()),width,height);return"uploadsuccess";}```2.Python(Flask框架)示例:伪代码:```pythonfromflaskimportFlask,requestimportmysql.connectorfromdatetimeimportdatetimefromPILimportImageapp=Flask(__name__)@app.route('/upload',methods=['POST'])defupload_file():file=request.files['file']iffile:#获取基本属性filename=file.filenamefile_size=len(file.read())file.seek(0)#重置指针,因为读取后指针到了末尾file_type=file.content_type#可选:解析图片属性width,height=0,0iffile_typeandfile_type.startswith('image/'):image=Image.open(file.stream)width,height=image.sizefile.stream.seek(0)#重置文件指针#连接数据库db=mysql.connector.connect(host="localhost",user="your_username",password="your_password",database="your_database")cursor=db.cursor()sql="INSERTINTOfile_table(file_name,file_size,file_type,upload_time,width,height)VALUES(%s,%s,%s,%s,%s,%s)"upload_time=datetime.now()data=(filename,file_size,file_type,upload_time,width,height)cursor.execute(sql,data)db.commit()cursor.close()db.close()return'Uploadsuccessful'```注意事项:1.文件存储:上述代码仅存储文件属性。若需存储文件内容到数据库(如BLOB字段),则需要读取文件字节并插入(但通常不建议,推荐存储文件路径)。2.文件保存:上传的文件可能需要保存到服务器的某个目录,而数据库中存储路径。或者直接将文件内容转为二进制存入数据库(但大文件可能影响数据库性能)。3.安全性:文件名需要处理,避免路径遍历攻击;SQL使用参数化查询避免注入。4.大文件:需考虑上传大小限制(如引用[4]中提到的PHP配置,其他语言也有类似配置)。如果用户想了解特定文件格式(如Excel)的解析,则可以结合引用[5]的方法读取Excel内容并存储,但用户的问题重点是“文件属性”,所以以上示例以基本属性和图片宽高为例。生成相关问题:1.如何获取上传文件的其他属性(如音频时长、文档页数)?2.如何将上传的文件保存到服务器目录,并在数据库中存储路径?3.如何限制上传文件的大小和类型?4.如何处理多个文件上传并批量存储属性?5.在存储文件属性时,如何避免覆盖同名文件?回答结构:1.解释实现步骤。2.提供两种语言(Java和Python)的示例代码。3.给出注意事项。4.提出相关问题。注意:引用[^1]到[^5]分别对应上述引用内容,但用户问题中并没有要求使用特定的引用内容,而是作为参考。我们在回答中可以提及参考了这些引用中的技术点。</think> ### 上传文件后识别属性并存储到数据库的实现方法 #### 核心实现步骤(语言无关) 1. **接收上传文件** - 通过HTTP请求接收文件流(Java/Python/PHP均支持)[^1][^4] 2. **识别文件属性** ```python # Python示例:获取基础属性 filename = file.filename file_size = len(file.read()) file_type = filename.split('.')[-1] # 扩展名 mime_type = file.content_type # MIME类型 ``` 3. **解析高级属性**(可选) ```java // Java示例:解析Excel文件 Workbook workbook = WorkbookFactory.create(inputStream); Sheet sheet = workbook.getSheetAt(0); int rowCount = sheet.getPhysicalNumberOfRows(); ``` 4. **数据库存储** ```sql /* MySQL建表示例 */ CREATE TABLE file_meta ( id INT AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(255), size BIGINT, filetype VARCHAR(20), upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, custom_meta JSON /* 存储额外属性 */ ); ``` #### 具体语言实现 ##### Python方案(Flask框架) ```python from flask import Flask, request import mysql.connector from werkzeug.utils import secure_filename app = Flask(__name__) @app.route('/upload', methods=['POST']) def upload(): file = request.files['file'] # 获取基础属性 meta = { "filename": secure_filename(file.filename), "size": len(file.read()), "mimetype": file.mimetype, "extension": file.filename.rsplit('.', 1)[1].lower() } # 连接数据库 [^3] db = mysql.connector.connect( host="localhost", user="user", password="pass", database="files_db" ) cursor = db.cursor() sql = """INSERT INTO file_meta (filename, size, filetype, custom_meta) VALUES (%s, %s, %s, %s)""" cursor.execute(sql, ( meta['filename'], meta['size'], meta['extension'], str(meta) # 将字典转为JSON字符串 )) db.commit() return "上传成功", 201 ``` ##### Java方案(Spring Boot) ```java @PostMapping("/upload") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) { // 获取文件属性 [^1][^5] String filename = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename())); long size = file.getSize(); String contentType = file.getContentType(); // 数据库操作 jdbcTemplate.update( "INSERT INTO file_meta (filename, size, filetype) VALUES (?, ?, ?)", filename, size, contentType ); return ResponseEntity.ok("File attributes saved"); } ``` #### 关键注意事项 1. **安全处理** - 使用`secure_filename`(Python)或`StringUtils.cleanPath`(Java)防止路径遍历攻击 - 限制文件类型:通过MIME类型白名单验证 2. **性能优化** - 大文件分块读取,避免内存溢出 - 数据库连接池管理(如HikariCP) 3. **高级属性扩展** - **图片**:使用Pillow获取尺寸/EXIF - **文档**:Apache POI读取页数/作者 - **多媒体**:FFmpeg提取时长/编码 #### 不同文件类型的属性提取 | 文件类型 | 可提取属性 | 工具建议 | |----------|----------------------------|------------------| | 图片 | 尺寸、DPI、色彩模式 | Pillow, ImageIO | | 视频 | 时长、编码、分辨率 | FFmpeg, OpenCV | | 文档 | 页数、作者、创建日期 | Apache POI, pdfminer | | 压缩包 | 包含文件数、压缩率 | zipfile, tarfile | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值