======= 结尾重要绿色链接,不看悔恨,和上面关联 ==========
JAVA 无需本地下载 Ffmpeg,实现 FfmpegCMD
本文不须要本地下载 Ffmpeg,只需引入 maven 依赖即可
1、增加 maven 依赖
<!--m3u8 视频转码须要 FFmpeg 依赖 -->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-win64</artifactId>
<version>3.3.1</version>
</dependency>
2、编写代码
package com.gxw.util;
import com.alibaba.fastjson.JSON;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.MultimediaInfo;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Description:(MP4 转码 HLS m3u8 AES128 加密)
* @author: GuoXiangWen
* @date: 2022 年 04 月 16 日 上午 08:08:08
*/
public class EncodeVideoUtils {
private static String PREIX = "gxwAwdlpgyn";
// 加密文件门路,如:enc.key、enc.keyinfo、m3u8 文件、ts 文件等
private static String ENC_DIRECTORY = "D:/test-ffmpeg/"+PREIX;
//enc.key 地址
private static String ENC_KEY_FILE_PATH = null;
//enc.keyinfo 地址
private static String ENC_KEY_INFO_FILE_PATH = null;
// 密钥 http 地址
private static String ENC_HTTP_PATH = "http://127.0.0.1:8848/Student";
// 执行胜利 0, 失败 1
private static int CODE_SUCCESS = 0;
private static int CODE_FAIL = 1;
//$encInfoPath、$encPath 是须要替换的 ENC_DIRECTORY 文件门路
private static String cmd_enc = "-y -i {videoPath} -hls_time 12 -hls_key_info_file $encInfoPath -hls_playlist_type vod -hls_segment_filename $encPath\\{tsName}_%3d.ts $encPath\\{saveM3u8Name}";
public static void main(String[] args) {
// 视频门路
String videoPath = "D:/test-ffmpeg/test.mp4";
System.out.println(JSON.toJSONString(encodeVideo(videoPath, "test", "1")));
}
/**
* 第一步:创立 enc.keyinfo 文件
* * 第二步:HLS m3u8 AES128 加密
* @param videoPath 视频门路
* @param videoType 视频所属类型,* @param index 视频角标,传来递增的
* @return
*/
public synchronized static Map<String, String> encodeVideo(String videoPath, String videoType, String index) {
String videoTypeBP = videoType;
String mp4Name = videoPath.substring(videoPath.lastIndexOf('/') + 1, videoPath.lastIndexOf(".mp4"));
String saveM3u8Name = PREIX + RandomUtils.generateRandomString(32);
// 设置 tsName
cmd_enc = cmd_enc.replace("{tsName}", saveM3u8Name);
// 设置视频地址
cmd_enc = cmd_enc.replace("{videoPath}", videoPath);
// 设置视频保留名称地址
cmd_enc = cmd_enc.replace("{saveM3u8Name}", saveM3u8Name + ".m3u8");
if(videoType.equals("") || videoType == null){videoType = "";}else {videoType = "/" + Md5Utils.md5Password(videoType, 8);
}
ENC_DIRECTORY = ENC_DIRECTORY + videoType + "/" + index;
ENC_KEY_FILE_PATH = ENC_DIRECTORY + "/" + saveM3u8Name + ".key";
ENC_KEY_INFO_FILE_PATH = ENC_DIRECTORY + "/" + saveM3u8Name + ".keyinfo";
ENC_HTTP_PATH = ENC_HTTP_PATH + "/" + PREIX + videoType + "/" + index + "/" + saveM3u8Name + ".key";
// 先创立两个流动线程的线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 异步执行
// 第一步:创立 enc.keyinfo 文件等
CompletableFuture<String> completableFutureTask = CompletableFuture.supplyAsync(() ->{
// 保留加密视频目录 enc
File encFilePathDir = new File(ENC_DIRECTORY);
if (!encFilePathDir.exists()) {// 判断目录是否存在
encFilePathDir.mkdirs();}
// 写入文件内容 enc.key
BufferedWriter bwkey = null;
// 写入文件内容 enc.keyinfo
BufferedWriter bwkeyInfo = null;
try{// 文件
File fileKey = new File(ENC_KEY_FILE_PATH);
File fileKeyInfo = new File(ENC_KEY_INFO_FILE_PATH);
// 初始化存在删除
if(fileKey.exists()) {fileKey.delete();
}
if(fileKeyInfo.exists()) {fileKeyInfo.delete();
}
bwkey = new BufferedWriter(new FileWriter(fileKey));
bwkeyInfo = new BufferedWriter(new FileWriter(fileKeyInfo));
// 写入 key-- 自定义的 AES128 加密的密匙
bwkey.write("******** 换成本人加密钥匙即可 *******");
// 写入 keyInfo
// 密匙 URL 地址,能够对该 URL 鉴权
bwkeyInfo.write(ENC_HTTP_PATH);
bwkeyInfo.newLine();
// 全门路,绝对路径
bwkeyInfo.write(ENC_KEY_FILE_PATH);
bwkey.flush();
bwkeyInfo.flush();}catch(IOException e){e.printStackTrace();
// 复原默认
ENC_KEY_INFO_FILE_PATH = null;
} finally{
try {
// 肯定要敞开文件
bwkey.close();
bwkeyInfo.close();} catch (IOException e) {e.printStackTrace();
}
}
return ENC_KEY_INFO_FILE_PATH;
}, executor).exceptionally(e -> {System.out.println(e);
return "false";
});
// 异步执行
// 第二步:HLS m3u8 AES128 加密
CompletableFuture<Integer> completableFutureTaskHls = completableFutureTask.thenApplyAsync((String encKeyInfoFilePath)->{if(encKeyInfoFilePath == null || encKeyInfoFilePath.length() == 0) {return CODE_FAIL;}
Integer codeTmp = cmdExecut(cmd_enc.replace("$encInfoPath", encKeyInfoFilePath).replace("$encPath", ENC_DIRECTORY));
if(CODE_SUCCESS != codeTmp) {return CODE_FAIL;}
System.out.println("类型:"+videoTypeBP+"视频:" + mp4Name + "加密, 胜利!");
return codeTmp;
}, executor).exceptionally(e -> {System.out.println(e);
return null;
});
// 获取执行后果
//code= 0 示意失常
try {System.out.println(String.format("获取最终执行后果:%s", completableFutureTaskHls.get() == CODE_SUCCESS ? "胜利!" : "失败!"));
} catch (InterruptedException e) {Thread.currentThread().interrupt();
e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();
}
// 返回后果
Map<String, String> resultMap = new HashMap<>();
// 视频名称
resultMap.put("videoName", mp4Name);
// 视频大小
resultMap.put("videoSize", getVideoSize(new File(videoPath)));
// 视频时长
resultMap.put("videoTime", getVideoTime(videoPath));
// 保留 m3u8 加密地址
resultMap.put("saveM3u8Path", ENC_DIRECTORY + "/" + saveM3u8Name + ".m3u8");
return resultMap;
}
/**
*
* @Description: (执行 ffmpeg 自定义命令)
* @param: @param cmdStr
* @param: @return
* @return: Integer
* @throws
*/
public static Integer cmdExecut(String cmdStr) {
//code= 0 示意失常
Integer code = null;
FfmpegCmd ffmpegCmd = new FfmpegCmd();
/**
* 谬误流
*/
InputStream errorStream = null;
try {ffmpegCmd.execute(false, true, cmdStr);
errorStream = ffmpegCmd.getErrorStream();
// 打印过程
int len = 0;
while ((len=errorStream.read())!=-1){System.out.print((char)len);
}
//code= 0 示意失常
code = ffmpegCmd.getProcessExitCode();} catch (IOException e) {e.printStackTrace();
} finally {
// 敞开资源
ffmpegCmd.close();}
// 返回
return code;
}
/**
* 获取视频时长
*
* @param FileUrl
* @return
*/
public static String getVideoTime(String FileUrl) {File source = new File(FileUrl);
String length = "";
try {MultimediaObject instance = new MultimediaObject(source);
MultimediaInfo result = instance.getInfo();
long ls = result.getDuration() / 1000;
Integer hour = (int) (ls / 3600);
Integer minute = (int) (ls % 3600) / 60;
Integer second = (int) (ls - hour * 3600 - minute * 60);
String hr = hour.toString();
String mi = minute.toString();
String se = second.toString();
if (hr.length() < 2) {hr = "0" + hr;}
if (mi.length() < 2) {mi = "0" + mi;}
if (se.length() < 2) {se = "0" + se;}
length = hr + ":" + mi + ":" + se;
} catch (Exception e) {e.printStackTrace();
}
return length;
}
/**
* 获取视频大小
*
* @param source
* @return
*/
public static String getVideoSize(File source) {
FileChannel fc = null;
String size = "";
try {FileInputStream fis = new FileInputStream(source);
fc = fis.getChannel();
BigDecimal fileSize = new BigDecimal(fc.size());
size = fileSize.divide(new BigDecimal(1024 * 1024), 2, RoundingMode.HALF_UP) + "MB";
} catch (FileNotFoundException e) {e.printStackTrace();
} catch (IOException e) {e.printStackTrace();
} finally {if (null != fc) {
try {fc.close();
} catch (IOException e) {e.printStackTrace();
}
}
}
return size;
}
}
3、运行测试
取得以下后果,即胜利: