io流轉換為Multipart檔案
阿新 • • 發佈:2021-01-19
io流轉換為Multipart檔案
個人的話是運用到了minio檔案伺服器儲存檔案,前端(vue)非同步上傳檔案後,由於要提升使用者體驗效果,先上傳檔案到後臺伺服器,返回視訊在檔案伺服器的link()引數,然後儲存該檔案到資料庫,然後運用java 定時任務之一 @Scheduled註解(如何使用自行www.baidu.com),定時進行在後臺轉碼相關視訊檔案。運用到的轉碼工具是FFmpeg.exe。
詳細如下:
- 新建一份工具類檔案,進行轉碼操作(親測轉碼各引數都有用),個人的話是統一轉碼為MP4檔案。
package org.springblade.common.utils;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springblade.common.config.loadFileConfig; import org.springblade.common.service.ResourceService; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.tool.api.R; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.util.ArrayList; import java.util.Date; import java.util.List; import static jodd.io.FileUtil.deleteFile; /** * Created by liuminghui on 2020/12/30 22:14 * * @annotate: * @Version 1.0*/ public class DisposeVideoUtils { private static final Logger logger = LoggerFactory.getLogger(DisposeVideoUtils.class); // 轉碼ffmpeg.exe工具路徑 private static final String FFMPEG_PATH = "F:\\gugexaizai\\acutvideo\\ffmpeg.exe"; // 轉碼mencoder.exe工具路徑 private static final String MENCODER_PATH = "F:\\gugexaizai\\acutvideo\\mencoder.exe"; // 臨時的視訊儲存路徑,轉碼完成後可刪除 private static final String TEMPORARY_VIDEO_PATH = "F:\\Images\\"; // 轉碼成功mp4視訊存放路徑 private static final String uploadFolder="F:\\ImagesPath\\"; // 轉碼後臺的視訊訪問路徑 private String videoUrl; // 視訊大小 private String size; private String filerealname; private String PATH; // 視訊截圖路徑 private String videoImg; public DisposeVideoUtils() { } //重構構造方法,傳入視訊存放路徑 public DisposeVideoUtils(String path) { videoUrl = path; } //set和get方法傳遞path public String getVideoPath() { return videoUrl; } public String getSize() { return size; } public void setSize(String size) { this.size = size; } public void setVideoPath(String path) { videoUrl = path; } public String getVideoImg() { return videoImg; } public void setVideoImg(String videoImg) { this.videoImg = videoImg; } /** * 轉碼、截圖和刪除原始檔功能 * @param tempPath 臨時源視訊檔案路徑 */ // public String runConver(String tempPath) { // logger.info("=================轉碼過程開始====================="); // try { // String targetExtension = ".mp4"; // 設定轉換的格式 // boolean isDelSourseFile = true; // // 轉碼、截圖和刪除原始檔 // boolean beginConver = beginVideoConver(tempPath, targetExtension, isDelSourseFile); // if (beginConver) { // logger.info("=================轉碼過程徹底結束====================="); // return videoUrl; // } // } catch (Exception e) { // e.printStackTrace(); // } // return null; // } /** * 視訊轉碼、截圖和刪除原視訊處理 * @param tempPath 臨時源視訊路徑 * @param targetExtension 轉碼成功的視訊字尾名 * @param isDelSourseFile 轉換完成後是否刪除原始檔 * @return */ // private boolean beginVideoConver(String tempPath, String targetExtension, boolean isDelSourseFile) { // File file = new File(tempPath); // String fileName = file.getName(); //獲取檔名+字尾名 // String fileNameSuffix = fileName.substring(fileName.lastIndexOf(".") + 1); // 獲取需要轉碼的檔案字尾 // //執行轉碼機制 // if (process(tempPath, fileNameSuffix, targetExtension)) { // //刪除臨時視訊或轉碼原視訊 // if (isDelSourseFile) { // // 刪除臨時檔案 // File fileDelete = new File(TEMPORARY_VIDEO_PATH); // String[] tempList = fileDelete.list(); // File temp = null; // for (int i = 0; i < tempList.length; i++) { // if (TEMPORARY_VIDEO_PATH.endsWith(File.separator)) { // temp = new File(TEMPORARY_VIDEO_PATH + tempList[i]); // } else { // temp = new File(TEMPORARY_VIDEO_PATH + File.separator + tempList[i]); // } // if (temp.isFile() || temp.isDirectory()) { // temp.delete(); // 刪除資料夾裡面的檔案 // } // } // } // // return true; // } else { // return false; // } // } /** * 實際轉換視訊格式的方法 * @param tempPath 臨時源視訊檔案路徑 * @param fileNameSuffix 源視訊字尾名 * @param targetExtension 轉碼成功的視訊字尾名 * @return */ // private boolean process(String tempPath, String fileNameSuffix, String targetExtension) { // //先判斷視訊的型別-返回狀態碼 // int type = checkVideoSuffix(fileNameSuffix); // boolean status = false; // // //根據狀態碼處理 // if (type == 0) { // logger.info("ffmpeg可以轉換,統一轉為mp4檔案"); // String status = processVideoFormat(tempPath, targetExtension);//可以指定轉換為什麼格式的視訊 // } else if (type == 1) { // //如果type為1,將其他檔案先轉換為avi,然後在用ffmpeg轉換為指定格式 // logger.info("ffmpeg不可以轉換,先呼叫mencoder轉碼avi"); // String avifilepath = processAVI(tempPath); // // if (avifilepath == null){ // // 轉碼失敗--avi檔案沒有得到 // logger.info("mencoder轉碼失敗,未生成AVI檔案"); // return false; // }else { // logger.info("生成AVI檔案成功,ffmpeg開始轉碼:"); // String status = processVideoFormat(avifilepath, targetExtension); // } // } // return status; //執行完成返回布林型別true // } /** * 轉換為指定格式 * ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等) * @param oldfilepath 臨時源視訊檔案路徑 * @param targetExtension 轉碼成功的視訊字尾名 .xxx * @return */ public String processVideoFormat(String oldfilepath, String targetExtension) { logger.info("呼叫了ffmpeg.exe工具"); // 先確保儲存轉碼後的視訊的mp4資料夾存在:TRANSCODE_VIDEO_MP4PATH-儲存路徑 String mp4Path = uploadFolder + Long.toString(new Date().getTime()) + targetExtension; File tempFile = new File(uploadFolder); if (tempFile.exists()) { if (tempFile.isDirectory()) { logger.info("該轉碼後的資料夾已存在"); } else { logger.info("同名的轉碼後的檔案存在,不能建立資料夾"); } } else { // 建立目錄 if (tempFile.mkdirs()) { logger.info("建立目錄轉碼後的資料夾:" + uploadFolder + ",成功!"); } else { logger.info("建立目錄轉碼後的資料夾:" + uploadFolder + ",失敗!"); } } List<String> commend = new ArrayList<String>(); commend.add(FFMPEG_PATH); // 新增轉換工具路徑 commend.add("-i"); // 新增引數"-i",該引數指定要轉換的檔案 commend.add(oldfilepath); // 新增要轉換格式的視訊檔案的路徑 commend.add("-vcodec"); commend.add("libx264"); commend.add("-acodec"); commend.add("aac"); commend.add("-ab"); commend.add("128k"); commend.add("-ar"); commend.add("16k"); commend.add("-ac"); commend.add("2"); commend.add("-f"); // 新增引數"-y",該引數指定將覆蓋已存在的檔案 commend.add("mp4"); // 新增引數"-y",該引數指定將覆蓋已存在的檔案 commend.add("-y"); // 新增引數"-y",該引數指定將覆蓋已存在的檔案 commend.add(mp4Path); // 列印命令 StringBuffer test = new StringBuffer(); for (int i = 0; i < commend.size(); i++) { test.append(commend.get(i) + " "); } logger.info("ffmpeg輸入的命令:" + test); try { String suffix = oldfilepath.substring(oldfilepath.lastIndexOf(".") + 1); if(checkVideoSuffix(suffix)==0){ // 多執行緒處理加快速度-解決rmvb資料丟失builder名稱要相同 ProcessBuilder builder = new ProcessBuilder(); builder.command(commend); builder.redirectErrorStream(true); Process process = builder.start(); // 多執行緒處理加快速度-解決資料丟失 // 執行緒處理 threadSyn(process); process.waitFor(); // 程序等待機制,必須要有,否則不生成mp4!!! logger.info("生成mp4視訊為:{}", mp4Path); // 生成mp4視訊儲存路徑 setVideoPath(mp4Path); logger.info("===============視訊轉碼結束================="); return mp4Path; } else if(checkVideoSuffix(suffix)==200){ return oldfilepath; } else{ return ("該檔案暫不支援轉換"); } } catch (Exception e) { e.printStackTrace(); return mp4Path; } } /** * 對ffmpeg無法解析的檔案格式(wmv9,rm,rmvb等), * 可以先用(mencoder)轉換為avi(ffmpeg能解析的)格式.再用ffmpeg解析為指定格式 * @param oldfilepath 臨時源視訊檔案路徑 * @return */ private String processAVI(String oldfilepath) { logger.info("呼叫了mencoder.exe工具"); String tempPath = TEMPORARY_VIDEO_PATH + Long.toString(new Date().getTime()); List<String> commend = new ArrayList<String>(); commend.add(MENCODER_PATH); // 指定mencoder.exe工具的位置 commend.add(oldfilepath); // 指定源視訊的位置 commend.add("-oac"); commend.add("mp3lame"); // lavc 原mp3lame commend.add("-lameopts"); commend.add("preset=64"); commend.add("-ovc"); commend.add("xvid"); // mpg4(xvid),AVC(h.264/x264),只有h264才是公認的MP4標準編碼,如果ck播放不了,就來調整這裡 commend.add("-xvidencopts"); // xvidencopts或x264encopts commend.add("bitrate=600"); // 600或440 commend.add("-of"); commend.add("avi"); commend.add("-o"); commend.add(tempPath + ".avi"); // 存放路徑+名稱,生成.avi視訊 // 打印出轉換命令 StringBuffer test = new StringBuffer(); for (int i = 0; i < commend.size(); i++) { test.append(commend.get(i) + " "); } logger.info("mencoder輸入的命令:" + test); try { // 呼叫執行緒命令啟動轉碼 ProcessBuilder builder = new ProcessBuilder(); builder.command(commend); Process process = builder.start(); // 多執行緒處理加快速度-解決資料丟失 // 執行緒處理 threadSyn(process); // 等Mencoder程序轉換結束,再呼叫ffmepg程序非常重要!!! process.waitFor(); logger.info("Mencoder程序結束"); return tempPath + ".avi"; // 返回轉為AVI以後的視訊地址 } catch (Exception e) { e.printStackTrace(); return null; } } /** * 視訊截圖功能 * @param sourceVideoPath 需要被截圖的視訊路徑(包含檔名和字尾名) * @return */ public boolean processImg(String sourceVideoPath) { String imageFile = "images/videoFile/"; String fileImpPath = ResourceService.rb.getString("filePath"); // 先確保儲存截圖的資料夾存在 File tempFile = new File(fileImpPath + imageFile); if (tempFile.exists()) { if (tempFile.isDirectory()) { logger.info("該截圖儲存資料夾存在。"); } else { logger.info("同名的截圖儲存檔案存在,不能建立資料夾。"); } } else { logger.info("截圖儲存資料夾不存在,建立該資料夾。"); tempFile.mkdir(); } File file = new File(sourceVideoPath); String fileName = file.getName(); // 獲取視訊檔案的名稱。 String fileRealName = fileName.substring(0, fileName.lastIndexOf(".")); // 獲取不加字尾名的視訊名 imageFile = imageFile + fileRealName + ".jpg"; List<String> commend = new ArrayList<String>(); // 第一幀: 00:00:01 // 截圖命令:time ffmpeg -ss 00:00:01 -i test1.flv -f image2 -y test1.jpg commend.add(FFMPEG_PATH); // 指定ffmpeg工具的路徑 commend.add("-ss"); commend.add("00:00:03"); // 3是代表第3秒的時候截圖 commend.add("-i"); commend.add(sourceVideoPath); // 截圖的視訊路徑 commend.add("-f"); commend.add("image2"); commend.add("-y"); commend.add(fileImpPath + imageFile); // 生成截圖xxx.jpg // 列印截圖命令 StringBuffer test = new StringBuffer(); for (int i = 0; i < commend.size(); i++) { test.append(commend.get(i) + " "); } logger.info("截圖命令:" + test); // 轉碼後完成截圖功能-還是得用執行緒來解決 try { // 呼叫執行緒處理命令 ProcessBuilder builder = new ProcessBuilder(); builder.command(commend); Process process = builder.start(); // 執行緒處理 threadSyn(process); // 等Mencoder程序轉換結束,再呼叫ffmepg程序非常重要!!! process.waitFor(); setVideoImg(imageFile); // 視訊大小 String videoSize = FileUploadToolUtil.getSize(file); setSize(videoSize); logger.info("截圖程序結束,視訊大小:{}", getSize()); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 多執行緒處理 * @param process */ public void threadSyn(Process process) { // 獲取程序的標準輸入流 final InputStream is1 = process.getInputStream(); // 獲取程序的錯誤流 final InputStream is2 = process.getErrorStream(); // 啟動兩個執行緒,一個執行緒負責讀標準輸出流,另一個負責讀標準錯誤流 new Thread() { public void run() { BufferedReader br = new BufferedReader(new InputStreamReader(is1)); try { String lineB = null; while ((lineB = br.readLine()) != null) { if (lineB != null) { logger.info(lineB); //必須取走執行緒資訊避免堵塞 } } } catch (IOException e) { e.printStackTrace(); } // 關閉流 finally { try { is1.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { BufferedReader br2 = new BufferedReader(new InputStreamReader(is2)); try { String lineC = null; while ((lineC = br2.readLine()) != null) { if (lineC != null) { logger.info(lineC); //必須取走執行緒資訊避免堵塞 } } } catch (IOException e) { e.printStackTrace(); } // 關閉流 finally { try { is2.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start(); } /** * 檢查檔案型別,檢查非MP4字尾 * asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等使用ffmpeg能解析 * wmv9,rm,rmvb等ffmpeg無法解析,先用別的工具(mencoder)轉換為avi(ffmpeg能解析的)格式 * @param suffixName 字尾名 * @return */ public int checkVideoSuffix(String suffixName) { if (suffixName.equals("avi")) { return 0; } else if (suffixName.equals("mpg")) { return 0; } else if (suffixName.equals("wmv")) { return 0; } else if (suffixName.equals("3gp")) { return 0; } else if (suffixName.equals("mov")) { return 0; } else if (suffixName.equals("asf")) { return 0; } else if (suffixName.equals("asx")) { return 0; } else if (suffixName.equals("flv")) { return 0; } else if (suffixName.equals("mkv")) { return 0; } else if (suffixName.equals("wmv9")) { return 1; } else if (suffixName.equals("rm")) { return 1; } else if (suffixName.equals("rmvb")) { return 1; } else if (suffixName.equals("mp4")) { // MP4不轉碼 return 200; } return 9; } /** * 視訊寫入本地磁碟/伺服器 * @param file 上傳檔案 * @param filePath 儲存位置 * @param fileName 檔名稱 * @return */ public boolean uploadVideo(MultipartFile file, String filePath, String fileName) { // 上傳到本地磁碟/伺服器 try { logger.info("上傳的視訊寫入本地磁碟/伺服器"); InputStream is = file.getInputStream(); OutputStream os = new FileOutputStream(new File(filePath, fileName)); int len = 0; byte[] buffer = new byte[2048]; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } os.close(); os.flush(); is.close(); return true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } // public void run() { // try { // // 轉換並截圖 // String filePath = "D:\\video\\old\\test.avi"; // DisposeVideoUtils cv = new DisposeVideoUtils(filePath); // cv.beginConver(); // // // 僅截圖 // // ProcessFlvImg pfi = new ProcessFlvImg(); // // pfi.processImg("D:\\video\\old\\test.avi"); // // } catch (Exception e) { // e.printStackTrace(); // } // } private boolean processFLV(String oldfilepath) { if (!checkfile(PATH)) { System.out.println(oldfilepath + " is not file"); return false; } List commend = new java.util.ArrayList(); commend.add(FFMPEG_PATH); commend.add("-i"); commend.add(oldfilepath); commend.add("-vcodec"); commend.add("libx264"); commend.add("-acodec"); commend.add("aac"); commend.add("-s"); commend.add("1280x720"); commend.add("-vprofile"); commend.add("high"); // commend.add("-vlevel"); // commend.add("3.1"); // commend.add("-coder"); // commend.add("1"); // -vcodec libx264 -acodec aac -ab 128k -ar 16k -ac 2 -f mp4 -y // commend.add("-movflags"); // commend.add("faststart"); // commend.add("-force_key_frames"); // commend.add("1"); // commend.add("-strict"); // commend.add("experimental"); commend.add("-r"); commend.add("25"); commend.add("-g"); commend.add("30"); commend.add("-bf"); commend.add("2"); commend.add("-ab"); commend.add("128k"); commend.add("-ar"); commend.add("44100"); commend.add("-ac"); commend.add("2"); // commend.add("-b:v"); // commend.add("1.6M"); // commend.add("-sc_threshold"); // commend.add("0"); commend.add("-f"); // 新增引數"-y",該引數指定將覆蓋已存在的檔案 commend.add("mp4"); // 新增引數"-y",該引數指定將覆蓋已存在的檔案 commend.add("-y"); commend.add(uploadFolder + filerealname + ".mp4"); try { ProcessBuilder builder = new ProcessBuilder(); String cmd = commend.toString(); builder.command(commend); Process p = builder.start(); doWaitFor(p); p.destroy(); deleteFile(oldfilepath); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public void deleteFile(String filepath) { File file = new File(filepath); if (PATH.equals(filepath)) { if (file.delete()) { System.out.println("檔案" + filepath + "已刪除"); } } else { if (file.delete()) { System.out.println("檔案" + filepath + "已刪除 "); } File filedelete2 = new File(PATH); if (filedelete2.delete()) { System.out.println("檔案" + PATH + "已刪除"); } } } private boolean checkfile(String path) { File file = new File(path); if (!file.isFile()) { return false; } else { return true; } } public int doWaitFor(Process p) { InputStream in = null; InputStream err = null; int exitValue = -1; // returned to caller when p is finished try { System.out.println("comeing"); in = p.getInputStream(); err = p.getErrorStream(); boolean finished = false; // Set to true when p is finished while (!finished) { try { while (in.available() > 0) { Character c = new Character((char) in.read()); System.out.print(c); } while (err.available() > 0) { Character c = new Character((char) err.read()); System.out.print(c); } exitValue = p.exitValue(); finished = true; } catch (IllegalThreadStateException e) { Thread.currentThread().sleep(500); } } } catch (Exception e) { System.err.println("doWaitFor();: unexpected exception - " + e.getMessage()); } finally { try { if (in != null) { in.close(); } } catch (IOException e) { System.out.println(e.getMessage()); } if (err != null) { try { err.close(); } catch (IOException e) { System.out.println(e.getMessage()); } } } return exitValue; } }
2. 將視訊檔案轉碼儲存在本地之後,因為沒有前端操作,一切都是在後臺默默的進行,所以我們要讀取該檔案(儲存在了本地磁碟),這必將是通過io流讀取檔案並獲得,但是由於minio的上傳方法要求是Multipart檔案,所以將io流轉換為Multipart檔案就可以了。
/** * * @Description io流轉換為MultipartFile---------返回MultipartFile檔案 * @return org.springframework.web.multipart.MultipartFile * @date 2020/12/30 * @auther liuminghui */ public static MultipartFile getFile( String filePath) throws IOException { File file = new File(filePath); FileItem fileItem = new DiskFileItem("copyfile.txt", Files.probeContentType(file.toPath()),false,file.getName(),(int)file.length(),file.getParentFile()); byte[] buffer = new byte[4096]; int n; try (InputStream inputStream = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()){ while ( (n = inputStream.read(buffer,0,4096)) != -1){ os.write(buffer,0,n); } //也可以用IOUtils.copy(inputStream,os); MultipartFile multipartFile = new CommonsMultipartFile(fileItem); System.out.println(multipartFile.getName()); return multipartFile; }catch (IOException e){ e.printStackTrace(); } return null; }
3, 大致就是這樣,還有很多檔案的引數的方法都可以在各大網站上搜索到的,這裡純屬是記錄一下工作中遇到的各種問題和最終解決,感謝平臺!