zqy
18 小时以前 072fff9b055a2b9e281961f28fe7fde572336c8a
修改bug
4个文件已修改
410 ■■■■ 已修改文件
ruoyi-admin/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/config/ElasticSearchConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhang-content/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhang-content/src/main/java/com/ruoyi/service/impl/VideoProcessService.java 398 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml
@@ -135,6 +135,12 @@
            <version>2.4.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.8</version>
        </dependency>
    </dependencies>
    <build>
ruoyi-common/src/main/java/com/ruoyi/common/config/ElasticSearchConfig.java
@@ -37,7 +37,6 @@
                        8087,
                    "http"
                )
zhang-content/pom.xml
@@ -127,6 +127,11 @@
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.8</version>
        </dependency>
    </dependencies>
zhang-content/src/main/java/com/ruoyi/service/impl/VideoProcessService.java
@@ -24,6 +24,13 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.Frame;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
@Service
@Slf4j
public class VideoProcessService {
@@ -248,23 +255,12 @@
    /**
     * 处理视频文件:生成Base64编码的封面
     * 处理视频文件:生成Base64编码的封面 (使用JavaCV)
     */
    public Map<String, Object> processVideo(File file, int width, int height,
                                            float quality, String format,boolean deleteY) {
                                            float quality, String format, boolean deleteY) {
        Map<String, Object> result = new HashMap<>();
        File tempVideoFile = file;
        System.out.println();
        try {
            // 1. 验证视频格式
@@ -274,21 +270,12 @@
                return result;
            }
            // 2. 保存原始文件到临时目录
//            String originalFilename = file.getName();
//            tempVideoFile = File.createTempFile("video_", ".temp");
            // 3. 获取视频信息
//            MultimediaInfo info = getVideoInfo(tempVideoFile);
//            double durationSeconds = info.getDuration() / 1000.0;
//            String videoResolution = info.getVideo().getSize().getWidth() + "x" + info.getVideo().getSize().getHeight();
            // 4. 生成Base64格式的封面
            String base64Thumbnail = generateBase64ThumbnailFromFile(
                tempVideoFile,
            // 2. 使用新的JavaCV方法生成Base64封面
            String base64Thumbnail = generateBase64ThumbnailWithJavaCV(
                file,
                width > 0 ? width : thumbnailWidth,
                height > 0 ? height : thumbnailHeight,
                (int) quality,
                quality, // 注意:这里传入float类型的quality
                format != null ? format : thumbnailFormat
            );
@@ -298,25 +285,15 @@
                return result;
            }
            // 5. 获取缩略图大小
            // 3. 获取缩略图大小 (方法保持不变,依赖Base64)
            String thumbnailSize = extractImageSizeFromBase64(base64Thumbnail);
            String[] sizeParts = thumbnailSize.split("x");
            int thumbWidth = sizeParts.length > 0 ? Integer.parseInt(sizeParts[0]) : 0;
            int thumbHeight = sizeParts.length > 1 ? Integer.parseInt(sizeParts[1]) : 0;
            // 6. 返回结果
            // 4. 返回结果
            result.put("success", true);
            result.put("type", "video");
            result.put("format", format != null ? format : thumbnailFormat);
//            result.put("originalFilename", originalFilename);
//            result.put("originalWidth", info.getVideo().getSize().getWidth());
//            result.put("originalHeight", info.getVideo().getSize().getHeight());
//            result.put("originalResolution", videoResolution);
//            result.put("duration", durationSeconds);
//            result.put("videoFormat", info.getFormat());
//            result.put("compressedWidth", thumbWidth);
//            result.put("compressedHeight", thumbHeight);
            result.put("base64", base64Thumbnail);
            result.put("thumbnailSize", thumbnailSize); // 可选,返回尺寸信息
            result.put("message", "视频封面生成成功");
        } catch (Exception e) {
@@ -325,24 +302,29 @@
            result.put("message", "封面生成失败: " + e.getMessage());
        } finally {
            // 清理临时文件
            if (tempVideoFile != null && tempVideoFile.exists() && deleteY) {
            if (deleteY && file != null && file.exists()) {
                try {
                    tempVideoFile.delete();
                    file.delete();
                } catch (Exception e) {
                    log.warn("删除临时视频文件时出错", e);
                }
            }
        }
        return result;
    }
    /**
     * 生成Base64格式的封面(可自定义参数)
     * 【核心】使用JavaCV生成Base64格式的封面
     */
    private String generateBase64ThumbnailFromFile(File videoFile, int width, int height,
                                                   int quality, String format) {
        File tempThumbnailFile = null;
        Process process = null;
    /**
     * 【核心】使用JavaCV生成Base64格式的封面
     */
    private String generateBase64ThumbnailWithJavaCV(File videoFile, int targetWidth,
                                                     int targetHeight, float quality,
                                                     String format) {
        FFmpegFrameGrabber grabber = null;
        Java2DFrameConverter converter = null;
        try {
            if (!videoFile.exists()) {
@@ -350,82 +332,308 @@
                return null;
            }
            // 1. 创建临时文件用于保存缩略图
            String ext = format != null ? format : thumbnailFormat;
            tempThumbnailFile = File.createTempFile("thumb_", "." + ext);
            tempThumbnailFile.deleteOnExit();
            // 1. 初始化抓取器
            grabber = new FFmpegFrameGrabber(videoFile);
            grabber.start();
            // 2. 获取视频时长,计算截图时间点
            MultimediaInfo info = getVideoInfo(videoFile);
            double durationSeconds = info.getDuration() / 1000.0;
            // 获取视频时长
            long durationMicroseconds = grabber.getLengthInTime();
            double durationSeconds = durationMicroseconds / 1_000_000.0;
            double screenshotTime = calculateScreenshotTime(durationSeconds);
            // 3. 构建FFmpeg命令
            String ffmpegCmd = String.format(
                "ffmpeg -y -i \"%s\" -ss %.2f -vframes 1 -q:v %d -s %dx%d -f image2 \"%s\"",
                videoFile.getAbsolutePath(),
                screenshotTime,
                quality,
                width,
                height,
                tempThumbnailFile.getAbsolutePath()
            );
            log.info("视频信息 - 文件名: {}, 时长: {}s, 截图时间: {}s, 原始尺寸: {}x{}",
                videoFile.getName(), durationSeconds, screenshotTime,
                grabber.getImageWidth(), grabber.getImageHeight());
            // 4. 执行FFmpeg命令
            ProcessBuilder processBuilder = new ProcessBuilder();
            String[] command = isWindows() ?
                new String[]{"cmd.exe", "/c", ffmpegCmd} :
                new String[]{"bash", "-c", ffmpegCmd};
            // 2. 跳转到指定时间点
            grabber.setTimestamp((long) (screenshotTime * 1_000_000));
            processBuilder.command(command);
            processBuilder.redirectErrorStream(true);
            process = processBuilder.start();
            // 3. 抓取帧
            Frame frame = null;
            BufferedImage bufferedImage = null;
            converter = new Java2DFrameConverter();
            // 5. 等待命令完成
            boolean finished = process.waitFor(30, TimeUnit.SECONDS);
            if (!finished) {
                process.destroyForcibly();
            // 尝试抓取多帧
            for (int i = 0; i < 10; i++) {
                frame = grabber.grabImage();
                if (frame != null && frame.image != null) {
                    bufferedImage = converter.convert(frame);
                    if (bufferedImage != null) {
                        log.info("成功抓取第{}帧,图片尺寸: {}x{}",
                            i, bufferedImage.getWidth(), bufferedImage.getHeight());
                        break;
                    }
                }
            }
            if (bufferedImage == null) {
                log.error("无法从视频中抓取有效图像帧");
                return null;
            }
            int exitCode = process.waitFor();
            if (exitCode != 0 || !tempThumbnailFile.exists() || tempThumbnailFile.length() == 0) {
            // 4. 调整图片尺寸
            int originalWidth = bufferedImage.getWidth();
            int originalHeight = bufferedImage.getHeight();
            if (targetWidth > 0 || targetHeight > 0) {
                bufferedImage = resizeImage(bufferedImage, targetWidth, targetHeight, true);
                log.info("调整尺寸: {}x{} -> {}x{}",
                    originalWidth, originalHeight,
                    bufferedImage.getWidth(), bufferedImage.getHeight());
            }
            // 5. 压缩并转换为Base64
            byte[] compressedBytes;
            String actualFormat = (format != null && !format.isEmpty()) ? format : "jpg";
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                if ("jpg".equalsIgnoreCase(actualFormat) || "jpeg".equalsIgnoreCase(actualFormat)) {
                    compressAsJpeg(bufferedImage, baos, quality);
                } else if ("png".equalsIgnoreCase(actualFormat)) {
                    compressAsPng(bufferedImage, baos, (int)(quality * 9));
                } else {
                    if (!ImageIO.write(bufferedImage, actualFormat, baos)) {
                        // 如果不支持该格式,回退到jpg
                        log.warn("不支持格式: {},回退到jpg", actualFormat);
                        actualFormat = "jpg";
                        baos.reset();
                        compressAsJpeg(bufferedImage, baos, quality);
                    }
                }
                compressedBytes = baos.toByteArray();
                if (compressedBytes == null || compressedBytes.length == 0) {
                    log.error("压缩后的图片数据为空");
                    return null;
                }
                log.info("图片压缩成功,大小: {} bytes", compressedBytes.length);
            } catch (Exception e) {
                log.error("图片压缩失败: {}", e.getMessage());
                // 尝试使用默认方式
                baos.reset();
                if (ImageIO.write(bufferedImage, actualFormat, baos)) {
                    compressedBytes = baos.toByteArray();
                } else {
                    return null;
                }
            }
            // 6. 构建Data URL
            String base64 = Base64.getEncoder().encodeToString(compressedBytes);
            if (base64 == null || base64.isEmpty()) {
                log.error("Base64编码失败");
                return null;
            }
            // 6. 读取图片文件并转换为Base64
            byte[] fileContent = Files.readAllBytes(tempThumbnailFile.toPath());
            String mimeType = getMimeType(actualFormat);
            String dataUrl = "data:" + mimeType + ";base64," + base64;
            if (!isValidImage(fileContent)) {
                return null;
            }
            log.info("生成Base64 Data URL成功,长度: {}, 格式: {}",
                dataUrl.length(), actualFormat);
            // 7. 转换为Base64
            String base64 = Base64.getEncoder().encodeToString(fileContent);
            String mimeType = getMimeType(ext);
            return "data:" + mimeType + ";base64," + base64;
            return dataUrl;
        } catch (Exception e) {
            log.error("生成Base64封面失败", e);
            log.error("使用JavaCV生成封面失败: {}", e.getMessage(), e);
            return null;
        } finally {
            // 清理资源
            if (process != null && process.isAlive()) {
                process.destroy();
            // 7. 释放资源
            if (converter != null) {
                try {
                    converter.close();
                } catch (Exception e) {
                    log.warn("关闭Java2DFrameConverter时出错", e);
                }
            }
            if (tempThumbnailFile != null && tempThumbnailFile.exists()) {
            if (grabber != null) {
                try {
                    tempThumbnailFile.delete();
                    grabber.stop();
                    grabber.release();
                } catch (Exception e) {
                    // ignore
                    log.warn("释放FFmpegFrameGrabber资源时出错", e);
                }
            }
        }
    }
//    /**
//     * 处理视频文件:生成Base64编码的封面
//     */
//    public Map<String, Object> processVideo(File file, int width, int height,
//                                            float quality, String format,boolean deleteY) {
//        Map<String, Object> result = new HashMap<>();
//        File tempVideoFile = file;
//
//
//        System.out.println();
//
//        try {
//            // 1. 验证视频格式
//            if (!isVideoFile(file)) {
//                result.put("success", false);
//                result.put("message", "不支持的视频格式");
//                return result;
//            }
//
//            // 2. 保存原始文件到临时目录
////            String originalFilename = file.getName();
////            tempVideoFile = File.createTempFile("video_", ".temp");
//
//            // 3. 获取视频信息
////            MultimediaInfo info = getVideoInfo(tempVideoFile);
////            double durationSeconds = info.getDuration() / 1000.0;
////            String videoResolution = info.getVideo().getSize().getWidth() + "x" + info.getVideo().getSize().getHeight();
//
//            // 4. 生成Base64格式的封面
//            String base64Thumbnail = generateBase64ThumbnailFromFile(
//                tempVideoFile,
//                width > 0 ? width : thumbnailWidth,
//                height > 0 ? height : thumbnailHeight,
//                (int) quality,
//                format != null ? format : thumbnailFormat
//            );
//
//            if (base64Thumbnail == null || base64Thumbnail.isEmpty()) {
//                result.put("success", false);
//                result.put("message", "封面生成失败");
//                return result;
//            }
//
//            // 5. 获取缩略图大小
//            String thumbnailSize = extractImageSizeFromBase64(base64Thumbnail);
//            String[] sizeParts = thumbnailSize.split("x");
//            int thumbWidth = sizeParts.length > 0 ? Integer.parseInt(sizeParts[0]) : 0;
//            int thumbHeight = sizeParts.length > 1 ? Integer.parseInt(sizeParts[1]) : 0;
//
//            // 6. 返回结果
//            result.put("success", true);
//            result.put("type", "video");
//            result.put("format", format != null ? format : thumbnailFormat);
////            result.put("originalFilename", originalFilename);
////            result.put("originalWidth", info.getVideo().getSize().getWidth());
////            result.put("originalHeight", info.getVideo().getSize().getHeight());
////            result.put("originalResolution", videoResolution);
////            result.put("duration", durationSeconds);
////            result.put("videoFormat", info.getFormat());
////            result.put("compressedWidth", thumbWidth);
////            result.put("compressedHeight", thumbHeight);
//            result.put("base64", base64Thumbnail);
//            result.put("message", "视频封面生成成功");
//
//        } catch (Exception e) {
//            log.error("视频封面生成失败", e);
//            result.put("success", false);
//            result.put("message", "封面生成失败: " + e.getMessage());
//        } finally {
//            // 清理临时文件
//            if (tempVideoFile != null && tempVideoFile.exists() && deleteY) {
//                try {
//                    tempVideoFile.delete();
//                } catch (Exception e) {
//                    log.warn("删除临时视频文件时出错", e);
//                }
//            }
//        }
//
//        return result;
//    }
//    /**
//     * 生成Base64格式的封面(可自定义参数)
//     */
//    private String generateBase64ThumbnailFromFile(File videoFile, int width, int height,
//                                                   int quality, String format) {
//        File tempThumbnailFile = null;
//        Process process = null;
//
//        try {
//            if (!videoFile.exists()) {
//                log.error("视频文件不存在: {}", videoFile.getAbsolutePath());
//                return null;
//            }
//
//            // 1. 创建临时文件用于保存缩略图
//            String ext = format != null ? format : thumbnailFormat;
//            tempThumbnailFile = File.createTempFile("thumb_", "." + ext);
//            tempThumbnailFile.deleteOnExit();
//
//            // 2. 获取视频时长,计算截图时间点
//            MultimediaInfo info = getVideoInfo(videoFile);
//            double durationSeconds = info.getDuration() / 1000.0;
//            double screenshotTime = calculateScreenshotTime(durationSeconds);
//
//            // 3. 构建FFmpeg命令
//            String ffmpegCmd = String.format(
//                "ffmpeg -y -i \"%s\" -ss %.2f -vframes 1 -q:v %d -s %dx%d -f image2 \"%s\"",
//                videoFile.getAbsolutePath(),
//                screenshotTime,
//                quality,
//                width,
//                height,
//                tempThumbnailFile.getAbsolutePath()
//            );
//
//            // 4. 执行FFmpeg命令
//            ProcessBuilder processBuilder = new ProcessBuilder();
//            String[] command = isWindows() ?
//                new String[]{"cmd.exe", "/c", ffmpegCmd} :
//                new String[]{"bash", "-c", ffmpegCmd};
//
//            processBuilder.command(command);
//            processBuilder.redirectErrorStream(true);
//            process = processBuilder.start();
//
//            // 5. 等待命令完成
//            boolean finished = process.waitFor(30, TimeUnit.SECONDS);
//            if (!finished) {
//                process.destroyForcibly();
//                return null;
//            }
//
//            int exitCode = process.waitFor();
//            if (exitCode != 0 || !tempThumbnailFile.exists() || tempThumbnailFile.length() == 0) {
//                return null;
//            }
//
//            // 6. 读取图片文件并转换为Base64
//            byte[] fileContent = Files.readAllBytes(tempThumbnailFile.toPath());
//
//            if (!isValidImage(fileContent)) {
//                return null;
//            }
//
//            // 7. 转换为Base64
//            String base64 = Base64.getEncoder().encodeToString(fileContent);
//            String mimeType = getMimeType(ext);
//
//            return "data:" + mimeType + ";base64," + base64;
//
//        } catch (Exception e) {
//            log.error("生成Base64封面失败", e);
//            return null;
//        } finally {
//            // 清理资源
//            if (process != null && process.isAlive()) {
//                process.destroy();
//            }
//
//            if (tempThumbnailFile != null && tempThumbnailFile.exists()) {
//                try {
//                    tempThumbnailFile.delete();
//                } catch (Exception e) {
//                    // ignore
//                }
//            }
//        }
//    }
    /**
     * 生成Base64格式的封面(不存储文件)
     */