安装FFmpeg步骤(Linux服务器)

CentOS 7

sudo yum install epel-release
sudo rpm -v --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
sudo yum install ffmpeg ffmpeg-devel

Ubuntu/Debian

sudo apt-get update
sudo apt-get install ffmpeg

验证安装

ffmpeg -version

安装FFmpeg步骤(Windows服务器)

下载FFmpeg
访问官网下载Windows版本:
https://ffmpeg.org/download.html
或直接下载完整版:
https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z

解压文件
将下载的压缩包解压,重命名为ffmpeg,并复制到C:\目录下,最终路径为C:\ffmpeg

添加环境变量

  1. 打开系统属性(Win + R,输入sysdm.cpl回车)
  2. 进入"高级系统设置" → “环境变量”
  3. 编辑系统变量中的Path,添加新路径:C:\ffmpeg\bin
  4. 保存所有窗口

验证安装
打开新的命令提示符窗口,运行:

ffmpeg -version

视频封面处理代码实现

核心方法

public Map<String, Object> processVideoCover(String videoUrl) {
    Map<String, Object> result = new HashMap<>();
    File tempVideoFile = null;
    File coverImageFile = null;

    try {
        // 下载视频到临时文件
        tempVideoFile = downloadVideoToTemp(videoUrl);
        if (tempVideoFile == null || !tempVideoFile.exists()) {
            result.put("status", "error");
            result.put("message", "视频下载失败");
            return result;
        }

        // 提取封面图
        coverImageFile = extractCoverWithFFmpegCommand(tempVideoFile);
        if (coverImageFile == null || !coverImageFile.exists()) {
            coverImageFile = extractCoverWithFFmpegAlternative(tempVideoFile);
        }

        if (coverImageFile == null || !coverImageFile.exists()) {
            result.put("status", "error");
            result.put("message", "封面图提取失败");
            return result;
        }

        // 上传到OSS
        Map<String, Object> ossResult = uploadCoverToOSS(coverImageFile);
            if (ossResult != null && ossResult.containsKey("filePath")) {
                result.put("status", "success");
                result.put("message", "封面图处理成功");
                result.put("coverUrl", ossResult.get("filePath"));
                result.put("fileName", ossResult.get("fileName"));
                result.put("videoUrl", videoUrl);
                result.put("coverSize", coverImageFile.length());

                System.out.println("封面图上传成功: " + ossResult.get("filePath"));
            } else {
                result.put("status", "error");
                result.put("message", "封面图上传到OSS失败");
            }
    } catch (Exception e) {
        result.put("status", "error");
        result.put("message", "处理错误: " + e.getMessage());
    } finally {
        // 清理临时文件
        if (tempVideoFile != null) tempVideoFile.delete();
        if (coverImageFile != null) coverImageFile.delete();
    }
    return result;
}

视频下载方法

private File downloadVideoToTemp(String videoUrl) throws IOException {
    String tempFileName = "video_" + System.currentTimeMillis() + "_" + 
        Math.abs(videoUrl.hashCode()) + ".mp4";
    File tempFile = new File(System.getProperty("java.io.tmpdir"), tempFileName);

    HttpURLConnection conn = null;
    InputStream in = null;
    FileOutputStream out = null;

    try {
        URL url = new URL(videoUrl);
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        in = conn.getInputStream();
        out = new FileOutputStream(tempFile);

        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
        return tempFile;
    } finally {
        if (in != null) in.close();
        if (out != null) out.close();
        if (conn != null) conn.disconnect();
    }
}

使用FFmpeg命令提取封面

    private File extractCoverWithFFmpegCommand(File videoFile) {
        try {
            // 生成封面文件名
            String coverFileName = "cover_" + System.currentTimeMillis() + ".jpg";
            File coverFile = new File(System.getProperty("java.io.tmpdir"), coverFileName);

            // CentOS系统FFmpeg路径 - 使用系统PATH中的ffmpeg
            // 如果安装了多个版本,可以指定完整路径
            String ffmpegCommand = "ffmpeg";  // 直接使用命令,系统会从PATH中查找

            // 也可以使用完整路径(如果知道确切位置)
            // String ffmpegCommand = "/usr/bin/ffmpeg";
            // 或者使用 which ffmpeg 查看具体位置:which ffmpeg

            // 构建FFmpeg命令 - 针对Linux系统优化
            // 注意:Linux下命令参数与Windows基本相同,但路径分隔符不同
            String[] cmd = {
                ffmpegCommand,
                "-i", videoFile.getAbsolutePath(),      // 输入文件
                "-ss", "00:00:01.000",                  // 跳转到第1秒
                "-vframes", "1",                        // 抓取1帧
                "-q:v", "2",                            // 图片质量
                "-vf", "scale='if(gt(iw,ih),1280,-1)':'if(gt(iw,ih),-1,720)'", // 缩放
                "-y",                                   // 覆盖输出
                coverFile.getAbsolutePath()             // 输出文件
            };

            System.out.println("执行FFmpeg命令: " + String.join(" ", cmd));

            // 在Linux上执行命令,可能需要设置环境变量
            ProcessBuilder pb = new ProcessBuilder(cmd);

            // 设置工作目录(可选)
            pb.directory(new File(System.getProperty("java.io.tmpdir")));

            // 合并标准输出和错误输出,方便读取
            pb.redirectErrorStream(true);

            // 执行命令
            Process process = pb.start();

            // 读取命令输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
                System.out.println("FFmpeg输出: " + line);
            }

            // 等待命令执行完成
            int exitCode = process.waitFor();
            System.out.println("FFmpeg命令执行完成,退出码: " + exitCode);

            if (exitCode != 0) {
                System.out.println("FFmpeg命令执行失败,输出:\n" + output.toString());

                // 检查是否是常见问题
                if (output.toString().contains("command not found")) {
                    System.out.println("FFmpeg未安装或不在PATH中");
                    System.out.println("请执行: which ffmpeg 查看FFmpeg位置");
                } else if (output.toString().contains("Permission denied")) {
                    System.out.println("权限被拒绝,请检查FFmpeg执行权限");
                    System.out.println("尝试执行: chmod +x /usr/bin/ffmpeg");
                } else if (output.toString().contains("Invalid data found")) {
                    System.out.println("视频文件格式可能不支持");
                }

                return null;
            }

            // 检查生成的封面文件
            if (!coverFile.exists()) {
                System.out.println("封面文件不存在");
                System.out.println("临时目录: " + System.getProperty("java.io.tmpdir"));
                System.out.println("期望的文件路径: " + coverFile.getAbsolutePath());

                // 列出临时目录内容,便于调试
                File tmpDir = new File(System.getProperty("java.io.tmpdir"));
                String[] tmpFiles = tmpDir.list();
                if (tmpFiles != null) {
                    System.out.println("临时目录内容:");
                    for (String file : tmpFiles) {
                        System.out.println("  " + file);
                    }
                }
                return null;
            }

            if (coverFile.length() == 0) {
                System.out.println("封面文件存在但大小为0");
                return null;
            }

            System.out.println("封面文件生成成功: " + coverFile.getAbsolutePath());
            System.out.println("封面文件大小: " + coverFile.length() + " bytes");
            return coverFile;

        } catch (Exception e) {
            System.out.println("执行FFmpeg命令失败: " + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 使用FFmpeg命令提取封面(备选方案)
     */
    private File extractCoverWithFFmpegAlternative(File videoFile) {
        try {
            // 生成封面文件名
            String coverFileName = "cover_alt_" + System.currentTimeMillis() + ".jpg";
            File coverFile = new File(System.getProperty("java.io.tmpdir"), coverFileName);

            // 构建FFmpeg命令 - 方案2:使用更简单的参数
            String[] cmd = {
                "ffmpeg",
                "-i", videoFile.getAbsolutePath(),    // 输入文件
                "-ss", "1",                           // 跳转到第1秒(简化格式)
                "-vframes", "1",                      // 抓取1帧
                "-f", "image2",                       // 输出格式为图片
                "-y",                                 // 覆盖输出文件
                coverFile.getAbsolutePath()           // 输出文件
            };

            System.out.println("执行FFmpeg备选命令: " + String.join(" ", cmd));

            // 执行命令
            Process process = Runtime.getRuntime().exec(cmd);

            // 读取错误输出
            BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            while (errorReader.readLine() != null) {
                // 只读取不显示,避免日志过多
            }

            // 等待命令执行完成
            int exitCode = process.waitFor();
            System.out.println("FFmpeg备选命令执行完成,退出码: " + exitCode);

            if (exitCode != 0) {
                return null;
            }

            // 检查生成的封面文件
            if (!coverFile.exists() || coverFile.length() == 0) {
                return null;
            }

            System.out.println("备选方案封面文件生成成功: " + coverFile.getAbsolutePath());
            return coverFile;

        } catch (Exception e) {
            System.out.println("执行FFmpeg备选命令失败: " + e.getMessage());
            return null;
        }
    }

将封面图下载到OSS

    private static Map<String, Object> uploadCoverToOSS(File coverFile) {
        Map<String, Object> result = new HashMap<>();

        try {
            // OSS配置 - 请替换为您的实际配置
            String endpoint = "**********";
            String accessKeyId = "**********";
            String accessKeySecret = "**********";
            String bucketName = "**********";

            // 生成文件名和路径
            String fileName = "video_covers/" +
                DateTimeUtil.getCurrentYear() + "/" +
                DateTimeUtil.getCurrentMonth() + "/" +
                DateTimeUtil.getCurrentDay() + "/" +
                System.currentTimeMillis() + ".jpg";

            // 创建OSS客户端
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

            // 设置上传元信息
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType("image/jpeg");
            metadata.setContentLength(coverFile.length());
            metadata.setCacheControl("max-age=2592000"); // 缓存30天

            // 上传文件
            PutObjectRequest request = new PutObjectRequest(bucketName, fileName, coverFile);
            request.setMetadata(metadata);

            PutObjectResult putResult = ossClient.putObject(request);

            // 构造访问URL
            String fileUrl = "https://" + bucketName + ".*********/" + fileName;

            result.put("filePath", fileUrl);
            result.put("fileName", fileName);
            result.put("ossRequestId", putResult.getRequestId());

            ossClient.shutdown();

            System.out.println("封面图上传到OSS成功: " + fileUrl);
            return result;

        } catch (Exception e) {
            System.out.println("上传封面图到OSS失败: " + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐