io流轉換為Multipart文件


    io流轉換為Multipart文件

    個人的話是運用到了minio文件服務器保存文件,前端(vue)異步上傳文件后,由於要提升用戶體驗效果,先上傳文件到后台服務器,返回視頻在文件服務器的link()參數,然后保存該文件到數據庫,然后運用java 定時任務之一 @Scheduled注解(如何使用自行www.baidu.com,定時進行在后台轉碼相關視頻文件。運用到的轉碼工具是FFmpeg.exe。

  詳細如下:

  1.   新建一份工具類文件,進行轉碼操作(親測轉碼各參數都有用),個人的話是統一轉碼為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,  大致就是這樣,還有很多文件的參數的方法都可以在各大網站上搜索到的,這里純屬是記錄一下工作中遇到的各種問題和最終解決,感謝平台!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM