From e1dc6930a9d217da8d87e2838208eb0e7eca2a2a Mon Sep 17 00:00:00 2001
From: zqy <2522236926@qq.com>
Date: 星期三, 31 十二月 2025 15:57:56 +0800
Subject: [PATCH] 新增mov

---
 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java       |    6 
 ruoyi-admin/pom.xml                                                             |   21 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java |  677 ++++++++++++++++++++++++++++++++
 ruoyi-common/src/main/java/com/ruoyi/common/config/ElasticSearchConfig.java     |    2 
 zhang-content/src/main/java/com/ruoyi/service/impl/VideoProcessService.java     |  514 ++++++++++++++++++++++++
 zhang-content/pom.xml                                                           |   13 
 6 files changed, 1,223 insertions(+), 10 deletions(-)

diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 544fea5..e128caf 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -109,6 +109,25 @@
             </exclusions>
         </dependency>
 
+
+        <dependency>
+            <groupId>net.coobird</groupId>
+            <artifactId>thumbnailator</artifactId>
+            <version>0.4.14</version>
+        </dependency>
+
+        <!-- JAVE 瑙嗛澶勭悊搴� -->
+        <dependency>
+            <groupId>ws.schild</groupId>
+            <artifactId>jave-core</artifactId>
+            <version>2.4.6</version>
+        </dependency>
+        <dependency>
+            <groupId>ws.schild</groupId>
+            <artifactId>jave-native-win64</artifactId>
+            <version>2.4.6</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -141,4 +160,4 @@
         <finalName>${project.artifactId}</finalName>
     </build>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
index 8e2e52c..461ee1f 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
@@ -7,8 +7,7 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.*;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -20,6 +19,7 @@
 import com.ruoyi.common.utils.RenamedMultipartFile;
 import com.ruoyi.common.utils.uuid.UUID;
 import com.ruoyi.service.DownLoadFileService;
+import com.ruoyi.service.impl.VideoProcessService;
 import lombok.Data;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,9 +36,20 @@
 import com.ruoyi.common.utils.file.FileUtils;
 import com.ruoyi.framework.config.ServerConfig;
 
+
+
+import javax.imageio.ImageIO;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+
 /**
  * 閫氱敤璇锋眰澶勭悊
- * 
+ *
  * @author ruoyi
  */
 @RestController
@@ -52,12 +63,47 @@
     @Autowired
     private DownLoadFileService downLoadFileService;
 
+
+    @Autowired
+    private VideoProcessService videoProcessService;
+
     private static final String FILE_DELIMETER = ",";
 
     private static final Pattern CHINESE_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");
 
+    // 缂╃暐鍥鹃厤缃�
+    @Value("${thumbnail.default-width:300}")
+    private int defaultThumbnailWidth;
 
-//    @GetMapping("/downloadFile")
+    @Value("${thumbnail.default-height:200}")
+    private int defaultThumbnailHeight;
+
+    @Value("${thumbnail.quality:0.8}")
+    private double thumbnailQuality;
+
+    @Value("${thumbnail.cache-dir:./cache/thumbnails}")
+    private String thumbnailCacheDir;
+
+
+    @Value("${thumbnail.max-width:1920}")
+    private int maxThumbnailWidth;
+
+    @Value("${thumbnail.max-height:1080}")
+    private int maxThumbnailHeight;
+
+    @Value("${thumbnail.format:jpg}")
+    private String thumbnailFormat;
+
+    @Value("${thumbnail.keep-aspect-ratio:true}")
+    private boolean keepAspectRatio;
+
+    @Anonymous
+    @GetMapping("/generateThumbnail")
+    public AjaxResult generateThumbnail(@PathParam(value = "url") String url) throws Exception {
+
+        return AjaxResult.success( videoProcessService.generateThumbnail(url));
+    }
+    //    @GetMapping("/downloadFile")
 //    public void fileDownload(@PathParam("path") String path, HttpServletResponse response)
 //    {
 //        path=path.substring(8);
@@ -444,4 +490,625 @@
         }
 
     }
-}
\ No newline at end of file
+
+
+
+
+
+
+    // 鏀寔鐨勫浘鐗囨牸寮�
+    private static final Set<String> SUPPORTED_IMAGE_FORMATS =
+        new HashSet<>(Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff"));    // 缂╃暐鍥剧紦瀛�
+    private final Map<String, Long> thumbnailCache = new HashMap<>();
+
+    /**
+     * 鍔ㄦ�佺敓鎴愮缉鐣ュ浘 - 涓诲叆鍙�
+     * 鍙傛暟璇存槑锛�
+     *   path: 鍥剧墖璺緞锛堝繀闇�锛�
+     *   width: 缂╃暐鍥惧搴︼紙鍙�夛紝榛樿300锛�
+     *   height: 缂╃暐鍥鹃珮搴︼紙鍙�夛紝榛樿200锛�
+     *   mode: 鐢熸垚妯″紡锛堝彲閫夛紝crop=瑁佸壀锛宻cale=缂╂斁锛岄粯璁cale锛�
+     *   quality: 鍥剧墖璐ㄩ噺锛堝彲閫夛紝0-1锛岄粯璁�0.8锛�
+     *   format: 杈撳嚭鏍煎紡锛堝彲閫夛紝jpg/png绛夛紝榛樿jpg锛�
+     */
+    @Anonymous
+    @GetMapping("/thumbnail")
+    public void generateThumbnail(
+        @RequestParam("path") String imagePath,
+        @RequestParam(value = "width", required = false) Integer width,
+        @RequestParam(value = "height", required = false) Integer height,
+        @RequestParam(value = "mode", defaultValue = "scale") String mode,
+        @RequestParam(value = "quality", required = false) Double quality,
+        @RequestParam(value = "format", required = false) String format,
+
+        HttpServletResponse response) {
+
+        try {
+            // 1. 鍙傛暟楠岃瘉鍜岃缃粯璁ゅ��
+            String decodedPath = URLDecoder.decode(imagePath, "UTF-8").replace("/profile","");
+
+            int targetWidth = width != null ? Math.min(width, maxThumbnailWidth) : defaultThumbnailWidth;
+            int targetHeight = height != null ? Math.min(height, maxThumbnailHeight) : defaultThumbnailHeight;
+            double targetQuality = quality != null ? Math.max(0.1, Math.min(1.0, quality)) : thumbnailQuality;
+            String targetFormat = format != null && SUPPORTED_IMAGE_FORMATS.contains(format.toLowerCase())
+                ? format.toLowerCase() : thumbnailFormat;
+
+            // 2. 瀹夊叏妫�鏌�
+            if (!FileUtils.checkAllowDownload(decodedPath)) {
+                response.sendError(HttpServletResponse.SC_FORBIDDEN, "绂佹璁块棶璇ヨ矾寰�");
+                return;
+            }
+
+            // 3. 鑾峰彇鍘熷浘鏂囦欢
+            String fullPath = RuoYiConfig.getProfile() + decodedPath;
+            File originalFile = new File(fullPath);
+
+            if (!originalFile.exists()) {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND, "鍥剧墖涓嶅瓨鍦�: " + decodedPath);
+                return;
+            }
+
+            if (!isImageFile(originalFile)) {
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "涓嶆槸鍥剧墖鏂囦欢: " + originalFile.getName());
+                return;
+            }
+
+
+
+            // 5. 鐢熸垚缂╃暐鍥�
+            BufferedImage thumbnail = generateThumbnailImage(
+                originalFile, targetWidth, targetHeight, mode, targetQuality, targetFormat
+            );
+
+            if (thumbnail == null) {
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "鐢熸垚缂╃暐鍥惧け璐�");
+                return;
+            }
+
+            // 7. 杈撳嚭缂╃暐鍥�
+            sendThumbnailResponse(thumbnail, targetFormat, targetQuality, response);
+
+        } catch (Exception e) {
+            log.error("鐢熸垚缂╃暐鍥惧け璐�: path={}, error={}", imagePath, e.getMessage(), e);
+            try {
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "鐢熸垚缂╃暐鍥惧け璐�: " + e.getMessage());
+            } catch (IOException ex) {
+                log.error("鍙戦�侀敊璇搷搴斿け璐�", ex);
+            }
+        }
+    }
+
+    /**
+     * 鎵归噺鐢熸垚缂╃暐鍥� - 閫氳繃閫楀彿鍒嗛殧鐨勮矾寰�
+     * 鍙傛暟璇存槑锛�
+     *   paths: 鐢ㄩ�楀彿鍒嗛殧鐨勫浘鐗囪矾寰勫垪琛紙蹇呴渶锛�
+     *   width: 缂╃暐鍥惧搴︼紙鍙�夛紝榛樿300锛�
+     *   height: 缂╃暐鍥鹃珮搴︼紙鍙�夛紝榛樿200锛�
+     *   mode: 鐢熸垚妯″紡锛堝彲閫夛紝crop=瑁佸壀锛宻cale=缂╂斁锛岄粯璁cale锛�
+     *   quality: 鍥剧墖璐ㄩ噺锛堝彲閫夛紝0-1锛岄粯璁�0.8锛�
+     *   format: 杈撳嚭鏍煎紡锛堝彲閫夛紝jpg/png绛夛紝榛樿jpg锛�
+     */
+    @Anonymous
+    @PostMapping("/batchThumbnailByPaths")
+    public AjaxResult generateBatchThumbnailByPaths(
+        @RequestParam("paths") String imagePaths,
+        @RequestParam(value = "width", required = false) Integer width,
+        @RequestParam(value = "height", required = false) Integer height,
+        @RequestParam(value = "mode", defaultValue = "scale") String mode,
+        @RequestParam(value = "quality", required = false) Double quality,
+        @RequestParam(value = "format", required = false) String format) {
+
+        List<Map<String, Object>> results = new ArrayList<>();
+        List<String> thumbnailUrls = new ArrayList<>(); // 瀛樺偍鎵�鏈夌缉鐣ュ浘URL
+
+        try {
+            // 1. 鍒嗗壊璺緞
+            String[] pathArray = imagePaths.split(",");
+            if (pathArray.length == 0) {
+                return AjaxResult.error("鍥剧墖璺緞涓嶈兘涓虹┖");
+            }
+
+            // 2. 璁剧疆鍘嬬缉鍙傛暟
+            int targetWidth = width != null ? Math.min(width, maxThumbnailWidth) : defaultThumbnailWidth;
+            int targetHeight = height != null ? Math.min(height, maxThumbnailHeight) : defaultThumbnailHeight;
+            double targetQuality = quality != null ? Math.max(0.1, Math.min(1.0, quality)) : thumbnailQuality;
+            String targetFormat = format != null && SUPPORTED_IMAGE_FORMATS.contains(format.toLowerCase())
+                ? format.toLowerCase() : thumbnailFormat;
+
+            int successCount = 0;
+            int failCount = 0;
+
+            // 3. 澶勭悊姣忎釜璺緞
+            for (String path : pathArray) {
+                Map<String, Object> result = new HashMap<>();
+                String trimmedPath = path.trim();
+
+                if (trimmedPath.isEmpty()) {
+                    continue;
+                }
+
+                result.put("originalPath", trimmedPath);
+
+                try {
+                    // 瑙g爜璺緞
+                    String decodedPath = URLDecoder.decode(trimmedPath, "UTF-8").replace("/profile","");
+
+                    // 瀹夊叏妫�鏌�
+                    if (!FileUtils.checkAllowDownload(decodedPath)) {
+                        result.put("success", false);
+                        result.put("error", "绂佹璁块棶璇ヨ矾寰�");
+                        result.put("code", 403);
+                        results.add(result);
+                        failCount++;
+                        continue;
+                    }
+
+                    // 鑾峰彇鍘熷浘
+                    String fullPath = RuoYiConfig.getProfile() + decodedPath;
+                    File originalFile = new File(fullPath);
+
+                    if (!originalFile.exists()) {
+                        result.put("success", false);
+                        result.put("error", "鍥剧墖涓嶅瓨鍦�");
+                        result.put("code", 404);
+                        results.add(result);
+                        failCount++;
+                        continue;
+                    }
+
+                    if (!isImageFile(originalFile)) {
+                        result.put("success", false);
+                        result.put("error", "涓嶆槸鍥剧墖鏂囦欢");
+                        result.put("code", 400);
+                        results.add(result);
+                        failCount++;
+                        continue;
+                    }
+
+                    // 鐢熸垚缂╃暐鍥綰RL
+                    String thumbnailUrl = buildThumbnailUrlWithParams(
+                        trimmedPath, targetWidth, targetHeight, mode, targetQuality, targetFormat
+                    );
+
+                    result.put("success", true);
+                    result.put("thumbnailUrl", thumbnailUrl);
+                    result.put("originalUrl", serverConfig.getUrl() + decodedPath);
+                    result.put("fileName", originalFile.getName());
+                    result.put("code", 200);
+
+                    // 灏嗘垚鍔熺殑缂╃暐鍥綰RL娣诲姞鍒板垪琛�
+                    thumbnailUrls.add(thumbnailUrl);
+
+                    // 鑾峰彇鍥剧墖淇℃伅
+                    BufferedImage originalImage = ImageIO.read(originalFile);
+                    if (originalImage != null) {
+                        result.put("originalWidth", originalImage.getWidth());
+                        result.put("originalHeight", originalImage.getHeight());
+                    }
+
+                    // 鍘嬬缉鍙傛暟
+
+                    Map<String, Object> compressParams = new HashMap<>();
+                    compressParams.put("width", targetWidth);
+                    compressParams.put("height", targetHeight);
+                    compressParams.put("mode", mode);
+                    compressParams.put("quality", targetQuality);
+                    compressParams.put("format", targetFormat);
+
+                    result.put("compressParams", compressParams);
+
+                    successCount++;
+
+                } catch (Exception e) {
+                    log.error("澶勭悊缂╃暐鍥捐姹傚け璐�: {}", trimmedPath, e);
+                    result.put("success", false);
+                    result.put("error", e.getMessage());
+                    result.put("code", 500);
+                    failCount++;
+                }
+
+                results.add(result);
+            }
+
+            // 4. 鏋勫缓杩斿洖缁撴灉
+            Map<String, Object> responseData = new HashMap<>();
+            responseData.put("results", results);
+
+            // 灏嗘墍鏈夋垚鍔熺殑缂╃暐鍥綰RL鐢ㄩ�楀彿杩炴帴
+            if (!thumbnailUrls.isEmpty()) {
+                responseData.put("thumbnailUrls", String.join(",", thumbnailUrls));
+            } else {
+                responseData.put("thumbnailUrls", "");
+            }
+
+            // 姹囨�讳俊鎭�
+            Map<String, Object> summary = new HashMap<>();
+            summary.put("total", pathArray.length);
+            summary.put("success", successCount);
+            summary.put("fail", failCount);
+            Map<String, Object> compressParams = new HashMap<>();
+            compressParams.put("width", targetWidth);
+            compressParams.put("height", targetHeight);
+            compressParams.put("mode", mode);
+            compressParams.put("quality", targetQuality);
+            compressParams.put("format", targetFormat);
+
+            summary.put("compressParams", compressParams);
+
+            responseData.put("summary", summary);
+
+            return AjaxResult.success(
+                String.format("鎵归噺澶勭悊瀹屾垚锛屾垚鍔�%s涓紝澶辫触%s涓�", successCount, failCount),
+                responseData
+            );
+
+        } catch (Exception e) {
+            log.error("鎵归噺鐢熸垚缂╃暐鍥惧け璐�", e);
+            return AjaxResult.error("鎵归噺澶勭悊澶辫触: " + e.getMessage());
+        }
+    }
+
+
+    /**
+     * 鎵归噺鐢熸垚缂╃暐鍥惧苟鐩存帴鍘嬬缉鍥剧墖 - 杩斿洖鍘嬬缉鍚庣殑鍥剧墖淇℃伅
+     */
+    @Anonymous
+    @PostMapping("/batchCompressImages")
+    public void batchCompressImages(
+        @RequestParam("paths") String imagePaths,
+        @RequestParam(value = "width", required = false) Integer width,
+        @RequestParam(value = "height", required = false) Integer height,
+        @RequestParam(value = "mode", defaultValue = "scale") String mode,
+        @RequestParam(value = "quality", required = false) Double quality,
+        @RequestParam(value = "format", required = false) String format,
+        HttpServletResponse response) {
+
+        try {
+            // 1. 鍒嗗壊璺緞
+            String[] pathArray = imagePaths.split(",");
+            if (pathArray.length == 0) {
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "鍥剧墖璺緞涓嶈兘涓虹┖");
+                return;
+            }
+
+            // 2. 璁剧疆鍘嬬缉鍙傛暟
+            int targetWidth = width != null ? Math.min(width, maxThumbnailWidth) : defaultThumbnailWidth;
+            int targetHeight = height != null ? Math.min(height, maxThumbnailHeight) : defaultThumbnailHeight;
+            double targetQuality = quality != null ? Math.max(0.1, Math.min(1.0, quality)) : thumbnailQuality;
+            String targetFormat = format != null && SUPPORTED_IMAGE_FORMATS.contains(format.toLowerCase())
+                ? format.toLowerCase() : thumbnailFormat;
+
+            // 3. 鐢熸垚ZIP鍘嬬缉鍖�
+            response.setContentType("application/zip");
+            response.setHeader("Content-Disposition", "attachment; filename=\"compressed_images.zip\"");
+
+            try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
+                int processedCount = 0;
+
+                for (String path : pathArray) {
+                    String trimmedPath = path.trim();
+                    if (trimmedPath.isEmpty()) {
+                        continue;
+                    }
+
+                    try {
+                        // 瑙g爜璺緞
+                        String decodedPath = URLDecoder.decode(trimmedPath, "UTF-8").replace("/profile","");
+
+                        // 瀹夊叏妫�鏌�
+                        if (!FileUtils.checkAllowDownload(decodedPath)) {
+                            log.warn("绂佹璁块棶璺緞: {}", decodedPath);
+                            continue;
+                        }
+
+                        // 鑾峰彇鍘熷浘
+                        String fullPath = RuoYiConfig.getProfile() + decodedPath;
+                        File originalFile = new File(fullPath);
+
+                        if (!originalFile.exists() || !isImageFile(originalFile)) {
+                            log.warn("鍥剧墖涓嶅瓨鍦ㄦ垨涓嶆槸鍥剧墖鏂囦欢: {}", decodedPath);
+                            continue;
+                        }
+
+                        // 鐢熸垚缂╃暐鍥�
+                        BufferedImage thumbnail = generateThumbnailImage(
+                            originalFile, targetWidth, targetHeight, mode, targetQuality, targetFormat
+                        );
+
+                        if (thumbnail != null) {
+                            // 娣诲姞鍒癦IP
+                            String fileName = getFileNameWithoutExtension(originalFile.getName()) +
+                                "_" + targetWidth + "x" + targetHeight +
+                                "." + targetFormat;
+
+                            ZipEntry zipEntry = new ZipEntry(fileName);
+                            zipOut.putNextEntry(zipEntry);
+
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            ImageIO.write(thumbnail, targetFormat, baos);
+                            zipOut.write(baos.toByteArray());
+
+                            zipOut.closeEntry();
+                            processedCount++;
+                        }
+
+                    } catch (Exception e) {
+                        log.error("澶勭悊鍥剧墖澶辫触: {}", trimmedPath, e);
+                    }
+                }
+
+                if (processedCount == 0) {
+                    response.reset(); // 娓呯┖鍝嶅簲
+                    response.setContentType("application/json");
+                    response.getWriter().write("{\"code\": 500, \"msg\": \"娌℃湁鍥剧墖澶勭悊鎴愬姛\"}");
+                }
+
+            } catch (Exception e) {
+                log.error("鐢熸垚鍘嬬缉鍖呭け璐�", e);
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "鐢熸垚鍘嬬缉鍖呭け璐�");
+            }
+
+        } catch (Exception e) {
+            log.error("鎵归噺鍘嬬缉鍥剧墖澶辫触", e);
+            try {
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "澶勭悊澶辫触: " + e.getMessage());
+            } catch (IOException ex) {
+                log.error("鍙戦�侀敊璇搷搴斿け璐�", ex);
+            }
+        }
+    }
+
+    /**
+     * 鑾峰彇鍥剧墖淇℃伅锛堝寘鍚缉鐣ュ浘URL锛�
+     */
+    @Anonymous
+    @GetMapping("/imageInfo")
+    public AjaxResult getImageInfo(
+        @RequestParam("path") String imagePath,
+        @RequestParam(value = "width", required = false) Integer width,
+        @RequestParam(value = "height", required = false) Integer height) {
+
+        try {
+            String decodedPath = URLDecoder.decode(imagePath, "UTF-8");
+
+            // 瀹夊叏妫�鏌�
+            if (!FileUtils.checkAllowDownload(decodedPath)) {
+                return AjaxResult.error("绂佹璁块棶璇ヨ矾寰�");
+            }
+
+            // 鑾峰彇鍘熷浘
+            String fullPath = RuoYiConfig.getProfile() + decodedPath;
+            File originalFile = new File(fullPath);
+
+            if (!originalFile.exists()) {
+                return AjaxResult.error("鍥剧墖涓嶅瓨鍦�");
+            }
+
+            if (!isImageFile(originalFile)) {
+                return AjaxResult.error("涓嶆槸鍥剧墖鏂囦欢");
+            }
+
+            // 璇诲彇鍥剧墖淇℃伅
+            BufferedImage image = ImageIO.read(originalFile);
+            if (image == null) {
+                return AjaxResult.error("鏃犳硶璇诲彇鍥剧墖");
+            }
+
+            // 鏋勫缓杩斿洖淇℃伅
+            Map<String, Object> info = new HashMap<>();
+            info.put("originalUrl", serverConfig.getUrl() + decodedPath);
+            info.put("originalPath", decodedPath);
+            info.put("fileName", originalFile.getName());
+            info.put("fileSize", originalFile.length());
+            info.put("fileType", getFileExtension(originalFile.getName()));
+            info.put("width", image.getWidth());
+            info.put("height", image.getHeight());
+            info.put("lastModified", originalFile.lastModified());
+
+            // 鏋勫缓缂╃暐鍥綰RL
+            int targetWidth = width != null ? width : defaultThumbnailWidth;
+            int targetHeight = height != null ? height : defaultThumbnailHeight;
+
+            String thumbnailUrl = buildThumbnailUrl(decodedPath, targetWidth, targetHeight);
+            info.put("thumbnailUrl", thumbnailUrl);
+
+            // 涓嶅悓灏哄鐨勭缉鐣ュ浘URL
+            info.put("smallThumbnailUrl", buildThumbnailUrl(decodedPath, 150, 100));
+            info.put("mediumThumbnailUrl", buildThumbnailUrl(decodedPath, 300, 200));
+            info.put("largeThumbnailUrl", buildThumbnailUrl(decodedPath, 600, 400));
+
+            return AjaxResult.success("鑾峰彇鎴愬姛", info);
+
+        } catch (Exception e) {
+            log.error("鑾峰彇鍥剧墖淇℃伅澶辫触: {}", imagePath, e);
+            return AjaxResult.error("鑾峰彇澶辫触: " + e.getMessage());
+        }
+    }
+
+
+
+    /**
+     * 妫�鏌ユ槸鍚︿负鍥剧墖鏂囦欢
+     */
+    private boolean isImageFile(File file) {
+        if (file == null || !file.exists()) {
+            return false;
+        }
+
+        String fileName = file.getName().toLowerCase();
+        String extension = getFileExtension(fileName);
+
+        return SUPPORTED_IMAGE_FORMATS.contains(extension);
+    }
+
+    /**
+     * 鑾峰彇鏂囦欢鎵╁睍鍚�
+     */
+    private String getFileExtension(String fileName) {
+        if (StringUtils.isEmpty(fileName)) {
+            return "";
+        }
+
+        int lastDot = fileName.lastIndexOf('.');
+        if (lastDot > 0 && lastDot < fileName.length() - 1) {
+            return fileName.substring(lastDot + 1).toLowerCase();
+        }
+        return "";
+    }
+
+
+    /**
+     * 鐢熸垚缂╃暐鍥�
+     */
+    private BufferedImage generateThumbnailImage(File originalFile, int width, int height,
+                                                 String mode, double quality, String format) {
+
+        try {
+            BufferedImage originalImage = ImageIO.read(originalFile);
+            if (originalImage == null) {
+                return null;
+            }
+
+            int originalWidth = originalImage.getWidth();
+            int originalHeight = originalImage.getHeight();
+
+            // 璁$畻鐩爣灏哄
+            int targetWidth = width;
+            int targetHeight = height;
+
+            if (keepAspectRatio && mode.equals("scale")) {
+                // 淇濇寔瀹介珮姣旂缉鏀�
+                double widthRatio = (double) width / originalWidth;
+                double heightRatio = (double) height / originalHeight;
+                double ratio = Math.min(widthRatio, heightRatio);
+
+                targetWidth = (int) (originalWidth * ratio);
+                targetHeight = (int) (originalHeight * ratio);
+            } else if (mode.equals("crop")) {
+                // 瑁佸壀妯″紡
+                double widthRatio = (double) width / originalWidth;
+                double heightRatio = (double) height / originalHeight;
+                double ratio = Math.max(widthRatio, heightRatio);
+
+                int cropWidth = (int) (width / ratio);
+                int cropHeight = (int) (height / ratio);
+
+                // 灞呬腑瑁佸壀
+                int cropX = (originalWidth - cropWidth) / 2;
+                int cropY = (originalHeight - cropHeight) / 2;
+
+                BufferedImage cropped = originalImage.getSubimage(
+                    Math.max(0, cropX),
+                    Math.max(0, cropY),
+                    Math.min(cropWidth, originalWidth - cropX),
+                    Math.min(cropHeight, originalHeight - cropY)
+                );
+
+                originalImage = cropped;
+            }
+
+            // 鍒涘缓鐩爣鍥剧墖
+            BufferedImage thumbnail = new BufferedImage(targetWidth, targetHeight,
+                format.equals("png") ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
+
+            // 缁樺埗缂╃暐鍥�
+            Graphics2D g2d = thumbnail.createGraphics();
+            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+            g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+                RenderingHints.VALUE_RENDER_QUALITY);
+            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+
+            g2d.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
+            g2d.dispose();
+
+            return thumbnail;
+
+        } catch (Exception e) {
+            log.error("鐢熸垚缂╃暐鍥惧け璐�: {}", originalFile.getAbsolutePath(), e);
+            return null;
+        }
+    }
+
+
+    /**
+     * 鍙戦�佺缉鐣ュ浘鍝嶅簲
+     */
+    private void sendThumbnailResponse(BufferedImage thumbnail, String format,
+                                       double quality, HttpServletResponse response) throws IOException {
+
+        response.setContentType("image/" + format);
+        response.setHeader("Cache-Control", "public, max-age=31536000"); // 缂撳瓨1骞�
+        response.setHeader("X-Thumbnail-Cache", "MISS");
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ImageIO.write(thumbnail, format, baos);
+
+        byte[] imageBytes = baos.toByteArray();
+        response.setContentLength(imageBytes.length);
+        response.getOutputStream().write(imageBytes);
+    }
+
+    /**
+     * 鏋勫缓缂╃暐鍥綰RL
+     */
+    private String buildThumbnailUrl(String imagePath, int width, int height) {
+        try {
+            return serverConfig.getUrl() + "/common/thumbnail?" +
+                "path=" + URLEncoder.encode(imagePath, "UTF-8") +
+                "&width=" + width +
+                "&height=" + height;
+        } catch (UnsupportedEncodingException e) {
+            return "";
+        }
+    }
+
+
+    /**
+     * 鏋勫缓甯﹀弬鏁扮殑缂╃暐鍥綰RL
+     */
+    private String buildThumbnailUrlWithParams(String imagePath, int width, int height,
+                                               String mode, double quality, String format)
+        throws UnsupportedEncodingException {
+
+        StringBuilder url = new StringBuilder();
+        url.append(serverConfig.getUrl()).append("/common/thumbnail?");
+        url.append("path=").append(URLEncoder.encode(imagePath, "UTF-8"));
+        url.append("&width=").append(width);
+        url.append("&height=").append(height);
+        url.append("&mode=").append(mode);
+        url.append("&quality=").append(quality);
+        url.append("&format=").append(format);
+
+        return url.toString();
+    }
+
+    /**
+     * 鑾峰彇涓嶅甫鎵╁睍鍚嶇殑鏂囦欢鍚�
+     */
+    private String getFileNameWithoutExtension(String fileName) {
+        if (fileName == null) {
+            return "";
+        }
+
+        int lastDot = fileName.lastIndexOf('.');
+        if (lastDot > 0) {
+            return fileName.substring(0, lastDot);
+        }
+
+        return fileName;
+    }
+    /**
+     * 缂╃暐鍥捐姹傚弬鏁扮被
+     */
+    @Data
+    static class ThumbnailRequest {
+        private String path;      // 鍥剧墖璺緞
+        private Integer width;    // 瀹藉害
+        private Integer height;   // 楂樺害
+        private String mode;      // 妯″紡锛歴cale/crop
+        private Double quality;   // 璐ㄩ噺锛�0-1
+        private String format;    // 鏍煎紡
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/ElasticSearchConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/ElasticSearchConfig.java
index 828d2d7..0bd9ae9 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/config/ElasticSearchConfig.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/ElasticSearchConfig.java
@@ -36,7 +36,7 @@
                         "localhost",
                         8087,
 
-
+//
                     "http"
 
                 )
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
index 9666615..297ef32 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
@@ -2,7 +2,7 @@
 
 /**
  * 濯掍綋绫诲瀷宸ュ叿绫�
- * 
+ *
  * @author ruoyi
  */
 public class MimeTypeUtils
@@ -16,7 +16,7 @@
     public static final String IMAGE_BMP = "image/bmp";
 
     public static final String IMAGE_GIF = "image/gif";
-    
+
     public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
 
     public static final String[] FLASH_EXTENSION = { "swf", "flv" };
@@ -34,7 +34,7 @@
             // 鍘嬬缉鏂囦欢
             "rar", "zip", "gz", "bz2",
             // 瑙嗛鏍煎紡
-            "mp4", "avi", "rmvb", "mp3","HEIC","WMF",
+            "mp4", "avi", "rmvb", "mp3","HEIC","WMF","mov",
             // pdf
             "pdf" ,
             "apk"};
diff --git a/zhang-content/pom.xml b/zhang-content/pom.xml
index c103ad2..09b5fce 100644
--- a/zhang-content/pom.xml
+++ b/zhang-content/pom.xml
@@ -106,6 +106,19 @@
             <artifactId>spring-boot-starter-mail</artifactId>
         </dependency>
 
+        <!-- JAVE 瑙嗛澶勭悊搴� -->
+        <dependency>
+            <groupId>ws.schild</groupId>
+            <artifactId>jave-core</artifactId>
+            <version>2.4.6</version>
+        </dependency>
+        <dependency>
+            <groupId>ws.schild</groupId>
+            <artifactId>jave-native-win64</artifactId>
+            <version>2.4.6</version>
+            <scope>runtime</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/zhang-content/src/main/java/com/ruoyi/service/impl/VideoProcessService.java b/zhang-content/src/main/java/com/ruoyi/service/impl/VideoProcessService.java
new file mode 100644
index 0000000..6229599
--- /dev/null
+++ b/zhang-content/src/main/java/com/ruoyi/service/impl/VideoProcessService.java
@@ -0,0 +1,514 @@
+package com.ruoyi.service.impl;
+
+import com.ruoyi.common.config.RuoYiConfig;
+import lombok.extern.slf4j.Slf4j;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import ws.schild.jave.*;
+
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+@Service
+@Slf4j
+public class VideoProcessService {
+
+    @Value("${video.upload-dir:./uploads/videos}")
+    private String uploadDir;
+
+    @Value("${video.thumbnail-dir:/profile/thumbnails}")
+    private String thumbnailDir;
+
+    @Value("${video.thumbnail.width:320}")
+    private int thumbnailWidth;
+
+    @Value("${video.thumbnail.height:240}")
+    private int thumbnailHeight;
+
+    @Value("${video.thumbnail.quality:5}")
+    private int thumbnailQuality; // 1-31锛屽�艰秺灏忚川閲忚秺濂�
+
+    @Value("${video.thumbnail.format:jpg}")
+    private String thumbnailFormat;
+
+    @Value("${video.thumbnail.count:1}")
+    private int thumbnailCount; // 鐢熸垚灏侀潰鏁伴噺
+
+    // 鏀寔鐨勮棰戞牸寮�
+    private static final Set<String> SUPPORTED_VIDEO_FORMATS =
+        new HashSet<>(Arrays.asList("mp4", "avi", "mov", "mkv", "flv", "wmv", "webm", "mpeg", "mpg", "3gp", "m4v"));
+
+    /**
+     * 澶勭悊瑙嗛鏂囦欢锛氬彧鐢熸垚灏侀潰
+     */
+    public VideoProcessResult processVideo(MultipartFile file) {
+        VideoProcessResult result = new VideoProcessResult();
+
+        try {
+            // 1. 楠岃瘉瑙嗛鏍煎紡
+            if (!isVideoFile(file)) {
+                result.setSuccess(false);
+                result.setMessage("涓嶆敮鎸佺殑瑙嗛鏍煎紡");
+                return result;
+            }
+
+            // 2. 鐢熸垚鍞竴鏂囦欢鍚�
+            String originalFilename = file.getOriginalFilename();
+            String fileExtension = getFileExtension(originalFilename);
+            String uuid = UUID.randomUUID().toString().replace("-", "");
+            String fileName = uuid + fileExtension;
+
+            // 3. 鍒涘缓鐩綍
+            createDirectories();
+
+            // 4. 淇濆瓨鍘熷鏂囦欢
+            Path originalPath = Paths.get(uploadDir, fileName);
+            file.transferTo(originalPath.toFile());
+
+            // 5. 鑾峰彇瑙嗛淇℃伅
+            MultimediaInfo info = getVideoInfo(originalPath.toFile());
+
+            // 璁剧疆缁撴灉
+            result.setSuccess(true);
+            result.setMessage("灏侀潰鐢熸垚鎴愬姛");
+            result.setOriginalPath(originalPath.toString());
+            result.setOriginalFilename(originalFilename);
+            result.setFileSize(file.getSize());
+            result.setDuration(info.getDuration() / 1000.0); // 杞崲涓虹
+            result.setResolution(info.getVideo().getSize().getWidth() + "x" + info.getVideo().getSize().getHeight());
+            result.setVideoFormat(info.getFormat());
+
+            // 6. 鐢熸垚灏侀潰
+            String thumbnailPath = generateThumbnail(originalPath.toFile().getPath());
+            result.setThumbnailPath(thumbnailPath);
+
+        } catch (Exception e) {
+            log.error("瑙嗛灏侀潰鐢熸垚澶辫触", e);
+            result.setSuccess(false);
+            result.setMessage("灏侀潰鐢熸垚澶辫触: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    /**
+     * 妫�鏌ユ槸鍚︿负鏀寔鐨勮棰戞枃浠�
+     */
+    private boolean isVideoFile(MultipartFile file) {
+        if (file == null || file.isEmpty()) {
+            return false;
+        }
+
+        String fileName = file.getOriginalFilename();
+        if (fileName == null) {
+            return false;
+        }
+
+        String extension = getFileExtension(fileName).toLowerCase();
+        return SUPPORTED_VIDEO_FORMATS.contains(extension);
+    }
+
+    /**
+     * 妫�鏌ユ槸鍚︿负鏀寔鐨勮棰戞枃浠�
+     */
+    private boolean isVideoFile(File file) {
+        if (file == null || !file.exists()) {
+            return false;
+        }
+
+        String fileName = file.getName();
+        String extension = getFileExtension(fileName).toLowerCase();
+        return SUPPORTED_VIDEO_FORMATS.contains(extension);
+    }
+
+    /**
+     * 鐢熸垚灏侀潰锛堢缉鐣ュ浘锛�
+     */
+    public String generateThumbnail(String videoPath) throws Exception {
+        File videoFile = new File(RuoYiConfig.getProfile() + videoPath.replace("/profile", ""));
+        if (!videoFile.exists()) {
+            throw new FileNotFoundException("瑙嗛鏂囦欢涓嶅瓨鍦�: " + videoPath);
+        }
+
+        // 鐢熸垚鍞竴鐨勭缉鐣ュ浘鏂囦欢鍚嶏紙涓庤棰戝悓鐩綍锛�
+        String originalName = videoFile.getName();
+        String baseName = getFileNameWithoutExtension(originalName);
+        String thumbnailName = "thumb_" + baseName + "_" +
+            UUID.randomUUID().toString().substring(0, 8) +
+            "." + thumbnailFormat;
+
+        // *** 鍏抽敭淇敼锛氱缉鐣ュ浘鐢熸垚鍦ㄨ棰戞枃浠舵墍鍦ㄧ洰褰� ***
+        // 鑾峰彇瑙嗛鏂囦欢鎵�鍦ㄧ殑鐩綍
+        File videoDir = videoFile.getParentFile();
+        if (videoDir == null) {
+            throw new RuntimeException("鏃犳硶鑾峰彇瑙嗛鏂囦欢鎵�鍦ㄧ洰褰�: " + videoFile.getAbsolutePath());
+        }
+
+        // 纭繚瑙嗛鐩綍瀛樺湪
+        if (!videoDir.exists()) {
+            videoDir.mkdirs();
+        }
+
+        // 鍒涘缓缂╃暐鍥炬枃浠跺璞★紙鍦ㄨ棰戝悓鐩綍锛�
+        File thumbnailFile = new File(videoDir, thumbnailName);
+
+        System.out.println("------------------------缂╃暐鍥炬枃浠跺悕: " + thumbnailName);
+        System.out.println("------------------------缂╃暐鍥惧畬鏁磋矾寰�: " + thumbnailFile.getAbsolutePath());
+        System.out.println("------------------------瑙嗛鏂囦欢鐩綍: " + videoDir.getAbsolutePath());
+
+        // 1. 鑾峰彇瑙嗛鏃堕暱锛岃绠楁埅鍥炬椂闂寸偣
+        MultimediaInfo info = getVideoInfo(videoFile);
+        double durationSeconds = info.getDuration() / 1000.0;
+        double screenshotTime = calculateScreenshotTime(durationSeconds);
+        System.out.println("------------------------鎴浘鏃堕棿鐐�: " + screenshotTime);
+
+        // 2. 鏋勫缓骞舵墽琛� FFmpeg 鍛戒护
+        String ffmpegCmd = String.format(
+            "ffmpeg -i \"%s\" -ss %.2f -vframes 1 -q:v %d -s %dx%d \"%s\"",
+            videoFile.getAbsolutePath(),  // 杈撳叆鏂囦欢
+            screenshotTime,               // 璺宠浆鍒扮殑鏃堕棿鐐癸紙绉掞級
+            thumbnailQuality,             // 杈撳嚭璐ㄩ噺 (2-31锛屽�艰秺灏忚川閲忚秺楂�)
+            thumbnailWidth,               // 杈撳嚭瀹藉害
+            thumbnailHeight,              // 杈撳嚭楂樺害
+            thumbnailFile.getAbsolutePath() // 杈撳嚭鏂囦欢锛堜笌瑙嗛鍚岀洰褰曪級
+        );
+
+
+        Process process = Runtime.getRuntime().exec(ffmpegCmd);
+
+        // 3. 璇诲彇骞舵墦鍗板懡浠よ緭鍑猴紙鐢ㄤ簬璋冭瘯锛�
+        try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+             BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+
+            String line;
+            while ((line = errorReader.readLine()) != null) {
+                System.out.println("[FFmpeg Error] " + line);
+            }
+            while ((line = inputReader.readLine()) != null) {
+                System.out.println("[FFmpeg Output] " + line);
+            }
+        }
+
+        int exitCode = process.waitFor();
+        System.out.println("------------------------FFmpeg 閫�鍑虹爜: " + exitCode);
+
+        if (exitCode != 0) {
+            throw new RuntimeException("FFmpeg 鎴浘澶辫触锛岄��鍑虹爜: " + exitCode);
+        }
+
+        if (!thumbnailFile.exists() || thumbnailFile.length() == 0) {
+            throw new RuntimeException("鐢熸垚鐨勭缉鐣ュ浘涓虹┖鎴栦笉瀛樺湪");
+        }
+
+        // 鑾峰彇缂╃暐鍥剧殑缁濆璺緞
+        String absolutePath = thumbnailFile.getAbsolutePath();
+
+        // 灏嗙粷瀵硅矾寰勮浆鎹负鐩稿profilePath鐨勭浉瀵硅矾寰�
+        String relativePath = absolutePath.substring(RuoYiConfig.getProfile().length()).replace("\\", "/");
+
+        // 纭繚鐩稿璺緞浠� / 寮�澶�
+        if (!relativePath.startsWith("/")) {
+            relativePath = "/" + relativePath;
+        }
+
+        System.out.println("------------------------鐢熸垚鐨勭浉瀵硅矾寰�: " + relativePath);
+
+        return "/profile"+relativePath;
+    }
+
+    /**
+     * 璁$畻鎴浘鏃堕棿鐐�
+     */
+    private double calculateScreenshotTime(double durationSeconds) {
+        // 1. 灏濊瘯鍦ㄧ1绉�
+        // 2. 濡傛灉瑙嗛涓嶈冻1绉掞紝鍦ㄤ腑闂存埅鍥�
+        // 3. 濡傛灉瑙嗛寰堢煭锛�<0.5绉掞級锛屽湪寮�澶存埅鍥�
+        double screenshotTime = 1.0; // 榛樿绗�1绉�
+        if (durationSeconds < 1.0) {
+            screenshotTime = durationSeconds / 2; // 涓棿
+        }
+        if (durationSeconds < 0.5) {
+            screenshotTime = 0; // 寮�澶�
+        }
+        return screenshotTime;
+    }
+
+    /**
+     * 鑾峰彇瑙嗛淇℃伅
+     */
+    private MultimediaInfo getVideoInfo(File videoFile) throws Exception {
+        MultimediaObject multimediaObject = new MultimediaObject(videoFile);
+        return multimediaObject.getInfo();
+    }
+
+
+    /**
+     * 鎵归噺鐢熸垚缂╃暐鍥�
+     */
+    public BatchThumbnailResult batchGenerateThumbnails(List<MultipartFile> files) {
+        BatchThumbnailResult result = new BatchThumbnailResult();
+        List<VideoProcessResult> results = new ArrayList<>();
+
+        int successCount = 0;
+        int failCount = 0;
+
+        for (MultipartFile file : files) {
+            try {
+                VideoProcessResult singleResult = processVideo(file);
+                results.add(singleResult);
+
+                if (singleResult.isSuccess()) {
+                    successCount++;
+                } else {
+                    failCount++;
+                }
+
+            } catch (Exception e) {
+                log.error("澶勭悊瑙嗛澶辫触: {}", file.getOriginalFilename(), e);
+                failCount++;
+            }
+        }
+
+        result.setResults(results);
+        result.setTotal(files.size());
+        result.setSuccess(successCount);
+        result.setFail(failCount);
+
+        return result;
+    }
+
+    /**
+     * 浠庤棰戠洰褰曟壒閲忕敓鎴愮缉鐣ュ浘
+     */
+    public void generateThumbnailsFromDirectory(File videoDir) throws Exception {
+        if (!videoDir.exists() || !videoDir.isDirectory()) {
+            throw new IllegalArgumentException("瑙嗛鐩綍涓嶅瓨鍦�");
+        }
+
+        File[] videoFiles = videoDir.listFiles((dir, name) -> isVideoFile(new File(dir, name)));
+
+        if (videoFiles == null || videoFiles.length == 0) {
+            log.warn("瑙嗛鐩綍涓病鏈夋壘鍒版敮鎸佺殑瑙嗛鏂囦欢");
+            return;
+        }
+
+        log.info("寮�濮嬫壒閲忕敓鎴愮缉鐣ュ浘锛屽叡{}涓棰�", videoFiles.length);
+
+        int successCount = 0;
+        int failCount = 0;
+
+        for (File videoFile : videoFiles) {
+            try {
+                generateThumbnail(videoFile.getPath());
+                successCount++;
+                log.info("宸茬敓鎴愮缉鐣ュ浘: {}", videoFile.getName());
+            } catch (Exception e) {
+                failCount++;
+                log.error("鐢熸垚缂╃暐鍥惧け璐�: {}", videoFile.getName(), e);
+            }
+        }
+
+        log.info("鎵归噺鐢熸垚缂╃暐鍥惧畬鎴愶紝鎴愬姛{}涓紝澶辫触{}涓�", successCount, failCount);
+    }
+
+    /**
+     * 鑾峰彇瑙嗛鏃堕暱
+     */
+    public long getVideoDuration(File videoFile) throws Exception {
+        MultimediaInfo info = getVideoInfo(videoFile);
+        return info.getDuration() / 1000; // 杩斿洖绉掓暟
+    }
+
+    /**
+     * 鑾峰彇瑙嗛鍒嗚鲸鐜�
+     */
+    public String getVideoResolution(File videoFile) throws Exception {
+        MultimediaInfo info = getVideoInfo(videoFile);
+        VideoSize size = info.getVideo().getSize();
+        return size.getWidth() + "x" + size.getHeight();
+    }
+
+    /**
+     * 鑾峰彇瑙嗛璇︾粏淇℃伅
+     */
+    public VideoInfo getVideoInfoDetails(File videoFile) throws Exception {
+        VideoInfo info = new VideoInfo();
+        MultimediaInfo multimediaInfo = getVideoInfo(videoFile);
+
+        info.setFileName(videoFile.getName());
+        info.setFileSize(videoFile.length());
+        info.setDuration(multimediaInfo.getDuration() / 1000.0);
+        info.setResolution(multimediaInfo.getVideo().getSize().getWidth() + "x" +
+            multimediaInfo.getVideo().getSize().getHeight());
+        info.setVideoFormat(multimediaInfo.getFormat());
+        info.setBitRate(multimediaInfo.getVideo().getBitRate());
+        info.setFrameRate(multimediaInfo.getVideo().getFrameRate());
+
+        return info;
+    }
+
+    /**
+     * 鑾峰彇鏂囦欢鎵╁睍鍚�
+     */
+    private String getFileExtension(String fileName) {
+        if (fileName == null) return "";
+        int lastDot = fileName.lastIndexOf('.');
+        if (lastDot > 0 && lastDot < fileName.length() - 1) {
+            return fileName.substring(lastDot + 1);
+        }
+        return "";
+    }
+
+    /**
+     * 鑾峰彇涓嶅甫鎵╁睍鍚嶇殑鏂囦欢鍚�
+     */
+    private String getFileNameWithoutExtension(String fileName) {
+        if (fileName == null) return "";
+        int lastDot = fileName.lastIndexOf('.');
+        if (lastDot > 0) {
+            return fileName.substring(0, lastDot);
+        }
+        return fileName;
+    }
+
+    /**
+     * 鏍规嵁鍥剧墖鏍煎紡鑾峰彇缂栫爜鍣�
+     */
+    private String getCodecForFormat(String format) {
+        switch (format.toLowerCase()) {
+            case "jpg":
+            case "jpeg":
+                return "mjpeg";
+            case "png":
+                return "png";
+            case "gif":
+                return "gif";
+            case "bmp":
+                return "bmp";
+            default:
+                return "png";
+        }
+    }
+
+    /**
+     * 鏍规嵁鎵╁睍鍚嶈幏鍙栨牸寮�
+     */
+    private String getFormatForExtension(String extension) {
+        switch (extension.toLowerCase()) {
+            case "jpg":
+            case "jpeg":
+                return "image2";
+            case "png":
+                return "image2";
+            case "gif":
+                return "gif";
+            case "bmp":
+                return "bmp";
+            default:
+                return "image2";
+        }
+    }
+
+    /**
+     * 鍒涘缓鐩綍
+     */
+    private void createDirectories() throws IOException {
+        Files.createDirectories(Paths.get(uploadDir));
+        Files.createDirectories(Paths.get(thumbnailDir));
+    }
+
+    @Data
+    public static class VideoProcessResult {
+        private boolean success;
+        private String message;
+        private String originalPath;
+        private String originalFilename;
+        private String thumbnailPath;
+        private List<String> thumbnailPaths; // 澶氫釜灏侀潰璺緞
+        private long fileSize;          // 鍘熷鏂囦欢澶у皬
+        private double duration;        // 瑙嗛鏃堕暱锛堢锛�
+        private String resolution;      // 瑙嗛鍒嗚鲸鐜�
+        private String videoFormat;     // 瑙嗛鏍煎紡
+
+        public String getFileSizeFormatted() {
+            return formatFileSize(fileSize);
+        }
+
+        public String getDurationFormatted() {
+            return formatDuration(duration);
+        }
+    }
+
+    @Data
+    public static class BatchThumbnailResult {
+        private List<VideoProcessResult> results;
+        private int total;
+        private int success;
+        private int fail;
+
+        public String getSuccessRate() {
+            if (total == 0) return "0%";
+            return String.format("%.1f%%", (success * 100.0) / total);
+        }
+    }
+
+    @Data
+    public static class VideoInfo {
+        private String fileName;
+        private long fileSize;
+        private double duration;        // 绉�
+        private String resolution;
+        private String videoFormat;
+        private int bitRate;           // 姣旂壒鐜�
+        private float frameRate;       // 甯х巼
+
+        public String getFileSizeFormatted() {
+            return formatFileSize(fileSize);
+        }
+
+        public String getDurationFormatted() {
+            return formatDuration(duration);
+        }
+    }
+
+    /**
+     * 鏍煎紡鍖栨枃浠跺ぇ灏�
+     */
+    public static String formatFileSize(long size) {
+        if (size < 1024) {
+            return size + " B";
+        } else if (size < 1024 * 1024) {
+            return String.format("%.1f KB", size / 1024.0);
+        } else if (size < 1024 * 1024 * 1024) {
+            return String.format("%.1f MB", size / (1024.0 * 1024.0));
+        } else {
+            return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
+        }
+    }
+
+    /**
+     * 鏍煎紡鍖栨椂闀�
+     */
+    public static String formatDuration(double seconds) {
+        if (seconds < 60) {
+            return String.format("%.1f绉�", seconds);
+        } else if (seconds < 3600) {
+            int minutes = (int) (seconds / 60);
+            double remainingSeconds = seconds % 60;
+            return String.format("%d鍒�%.1f绉�", minutes, remainingSeconds);
+        } else {
+            int hours = (int) (seconds / 3600);
+            int minutes = (int) ((seconds % 3600) / 60);
+            double remainingSeconds = seconds % 60;
+            return String.format("%d灏忔椂%d鍒�%.1f绉�", hours, minutes, remainingSeconds);
+        }
+    }
+}
+

--
Gitblit v1.9.1