免安装 FFmpeg
<dependency>
<groupId>ws.schildgroupId>
<artifactId>jave-all-depsartifactId>
<version>3.0.1version>
<exclusions>
<exclusion>
<groupId>ws.schildgroupId>
<artifactId>jave-nativebin-win32artifactId>
exclusion>
<exclusion>
<groupId>ws.schildgroupId>
<artifactId>jave-nativebin-linux32artifactId>
exclusion>
<exclusion>
<groupId>ws.schildgroupId>
<artifactId>jave-nativebin-osx64artifactId>
exclusion>
exclusions>
dependency>
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
* @author Mr.superbeyone
* @project
* @className FfmpegUtil
* @description
* @date 2023-10-19 13:37
**/
public class FfmpegUtil {
private static Logger logger = LoggerFactory.getLogger(FfmpegUtil.class);
/**
* 通过本地路径获取多媒体文件信息(宽,高,时长,编码等)
*
* @param localPath 本地路径
* @return MultimediaInfo 对象,包含 (宽,高,时长,编码等)
* @throws EncoderException
*/
public static MultimediaInfo getMultimediaInfo(String localPath) {
MultimediaInfo multimediaInfo = null;
try {
multimediaInfo = new MultimediaObject(new File(localPath)).getInfo();
} catch (EncoderException e) {
System.out.println("获取多媒体文件信息异常");
e.printStackTrace();
}
return multimediaInfo;
}
/**
* 通过URL获取多媒体文件信息
*
* @param url 网络url
* @return MultimediaInfo 对象,包含 (宽,高,时长,编码等)
* @throws EncoderException
*/
public static MultimediaInfo getMultimediaInfoFromUrl(String url) {
MultimediaInfo multimediaInfo = null;
try {
multimediaInfo = new MultimediaObject(new URL(url)).getInfo();
} catch (Exception e) {
System.out.println("获取多媒体文件信息异常");
e.printStackTrace();
}
return multimediaInfo;
}
private static final int SAMPLING_RATE = 16000;
private static final int SINGLE_CHANNEL = 1;
/**
* 音频格式化为wav,并设置单声道和采样率
*
* @param url 需要转格式的音频
* @param targetPath 格式化后要保存的目标路径
*/
public static boolean formatAudio(String url, String targetPath) {
File target = new File(targetPath);
MultimediaObject multimediaObject;
try {
// 若是本地文件: multimediaObject = new MultimediaObject(new File("你的本地路径"));
multimediaObject = new MultimediaObject(new URL(url));
// 音频参数
// 此处按需自定义音频参数
AudioAttributes audio = new AudioAttributes();
// 采样率
audio.setSamplingRate(SAMPLING_RATE);
// 单声道
audio.setChannels(SINGLE_CHANNEL);
Encoder encoder = new Encoder();
EncodingAttributes attrs = new EncodingAttributes();
// 输出格式
attrs.setOutputFormat("wav");
attrs.setAudioAttributes(audio);
encoder.encode(multimediaObject, target, attrs);
return true;
} catch (Exception e) {
System.out.println("格式化音频异常");
return false;
}
}
/**
* 视频格式化为mp4
*
* @param url
* @param targetPath
* @return
*/
public static boolean formatToMp4(String url, String targetPath) {
File target = new File(targetPath);
MultimediaObject multimediaObject;
try {
// 若是本地文件: multimediaObject = new MultimediaObject(new File("你的本地路径"));
multimediaObject = new MultimediaObject(new URL(url));
EncodingAttributes attributes = new EncodingAttributes();
// 设置视频的音频参数
AudioAttributes audioAttributes = new AudioAttributes();
attributes.setAudioAttributes(audioAttributes);
// 设置视频的视频参数
VideoAttributes videoAttributes = new VideoAttributes();
// 设置帧率
videoAttributes.setFrameRate(25);
attributes.setVideoAttributes(videoAttributes);
// 设置输出格式
attributes.setOutputFormat("mp4");
Encoder encoder = new Encoder();
encoder.encode(multimediaObject, target, attributes);
return true;
} catch (Exception e) {
System.out.println("格式化视频异常");
e.printStackTrace();
return false;
}
}
/**
* 获取视频缩略图 获取视频第0秒的第一帧图片
*
* 执行的ffmpeg 命令为: ffmpeg -i 你的视频文件路径 -ss 指定的秒数 生成文件的全路径地址
*
* @param localPath 本地路径
* @param targetPath 存放的目标路径
* @return
*/
public static boolean getTargetThumbnail(String localPath, String targetPath) {
// 该方法基本可作为执行ffmpeg命令的模板方法,之后的几个方法与此类似
try {
ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(localPath);
ffmpeg.addArgument("-ss");
// 此处可自定义视频的秒数
ffmpeg.addArgument("10");
ffmpeg.addArgument(targetPath);
ffmpeg.execute();
try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
blockFfmpeg(br);
}
} catch (IOException e) {
System.out.println("获取视频缩略图失败");
e.printStackTrace();
return false;
}
return true;
}
/**
* 等待命令执行成功,退出
*
* @param br
* @throws IOException
*/
private static void blockFfmpeg(BufferedReader br) throws IOException {
String line;
// 该方法阻塞线程,直至合成成功
while ((line = br.readLine()) != null) {
doNothing(line);
}
}
/**
* 打印日志
*
* @param line
*/
private static void doNothing(String line) {
// 正式使用时注释掉此行,仅用于观察日志
// System.out.println(line);
}
/**
* 视频增加字幕
*
* @param originVideoPath 原视频地址
* @param targetVideoPath 目标视频地址
* @param srtPath 固定格式的srt文件地址或存储位置,字母文件名: xxx.srt,样例看博客
* @return
* @throws Exception
*/
public static boolean addSubtitle(
String originVideoPath, String srtPath, String targetVideoPath) {
try {
ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(originVideoPath);
ffmpeg.addArgument("-i");
ffmpeg.addArgument(srtPath);
ffmpeg.addArgument("-c");
ffmpeg.addArgument("copy");
ffmpeg.addArgument(targetVideoPath);
ffmpeg.execute();
try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
blockFfmpeg(br);
}
} catch (IOException e) {
System.out.println("字幕增加失败");
e.printStackTrace();
}
return true;
}
/**
* 常用命令
*
* @return
*/
public static void cmd() {
// FIXME: 2023/1/31 还有很多类似命令 不再一一列举 ,附上命令,具体写法参考 getTargetThumbnail或addSubtitle方法
// FIXME: 2023/1/31 ffmpeg命令网上搜索即可
// 剪切视频
// ffmpeg -ss 00:00:00 -t 00:00:30 -i test.mp4 -vcodec copy -acodec copy output.mp4
// * -ss 指定从什么时间开始
// * -t 指定需要截取多长时间
// * -i 指定输入文件
// ffmpeg -ss 10 -t 15 -accurate_seek -i test.mp4 -codec copy cut.mp4
// ffmpeg -ss 10 -t 15 -accurate_seek -i test.mp4 -codec copy -avoid_negative_ts 1 cut.mp4
// 拼接MP4
// 第一种方法:
// ffmpeg -i "concat:1.mp4|2.mp4|3.mp4" -codec copy out_mp4.mp4
// 1.mp4 第一个视频文件的全路径
// 2.mp4 第二个视频文件的全路径
// 提取视频中的音频
// ffmpeg -i input.mp4 -acodec copy -vn output.mp3
// -vn: 去掉视频;-acodec: 音频选项, 一般后面加copy表示拷贝
// 音视频合成
// ffmpeg -y –i input.mp4 –i input.mp3 –vcodec copy –acodec copy output.mp4
// -y 覆盖输出文件
// 剪切视频
// ffmpeg -ss 0:1:30 -t 0:0:20 -i input.mp4 -vcodec copy -acodec copy output.mp4
// -ss 开始时间; -t 持续时间
// 视频截图
// ffmpeg –i test.mp4 –f image2 -t 0.001 -s 320x240 image-%3d.jpg
// -s 设置分辨率; -f 强迫采用格式fmt;
// 视频分解为图片
// ffmpeg –i test.mp4 –r 1 –f image2 image-%3d.jpg
// -r 指定截屏频率
// 将图片合成视频
// ffmpeg -f image2 -i image%d.jpg output.mp4
// 视频拼接
// ffmpeg -f concat -i filelist.txt -c copy output.mp4
// 将视频转为gif
// ffmpeg -i input.mp4 -ss 0:0:30 -t 10 -s 320x240 -pix_fmt rgb24 output.gif
// -pix_fmt 指定编码
// 视频添加水印
// ffmpeg -i input.mp4 -i logo.jpg
// -filter_complex[0:v][1:v]overlay=main_w-overlay_w-10:main_h-overlay_h-10[out] -map [out] -map
// 0:a -codec:a copy output.mp4
// main_w-overlay_w-10 视频的宽度-水印的宽度-水印边距;
}
//=========================================================================================//
private static final int WIDTH = 50, HEIGHT = 50;
public static String generateTagVideo(String srcVideoPath) {
long start = System.currentTimeMillis();
String targetVideo = generateTagVideo(srcVideoPath, null);
logger.info("generateTagVideo 耗时 {}", (System.currentTimeMillis() - start));
logger.info("generateTagVideo srcVideo {} targetVideo {}", srcVideoPath, targetVideo);
return targetVideo;
}
public static String generateTagVideo(String srcVideoPath, String targetVideoPath) {
long start = System.currentTimeMillis();
//视频文件路径
File srcVideoFile = new File(srcVideoPath);
if (!srcVideoFile.exists() || !srcVideoFile.isFile()) {
logger.info("generateTagVideo srcVideoPath is not video file {}", srcVideoPath);
return "";
}
//图片文件、视频保存跟路径
File genRoot = new File(StringUtils.isBlank(targetVideoPath) ? srcVideoFile.getParentFile().getAbsolutePath() : targetVideoPath,
StringUtils.replace(srcVideoFile.getName(), ".", "_") + "_gen"
);
String targetFileName = StringUtils.substringBeforeLast(srcVideoFile.getName(), ".") + "_gen" + ".mp4";
//判断是否存在已生成的视频文件
if (genRoot.exists() && genRoot.isDirectory()) {
Optional<File> optionalFile = Arrays.stream(Objects.requireNonNull(genRoot.listFiles()))
.filter(f -> StringUtils.equals(f.getName(), targetFileName)).findFirst();
if (optionalFile.isPresent()) {
return optionalFile.get().getAbsolutePath();
}
}
//图片路径
File imgDir = new File(genRoot, "img");
imgDir.mkdirs();
//视频文件拆分成图片
splitVideo2Img(srcVideoPath, imgDir.getAbsolutePath());
//获取要RPC调用的文件
List<File> imgList = getImgList(imgDir);
//TODO 跟进图片,比对后,获取图片及坐标信息
int x = 1000, y = 500;
//TODO 获取需要标注的图片文件集合,及标注坐标
// imgList = new ArrayList<>();
//替换文件
replaceImgFile(imgList, x, y, WIDTH, HEIGHT);
//合并图片文件为视频
String targetPath = mergeImg2Video(genRoot, targetFileName, imgDir.getAbsolutePath());
logger.info("generateTagVideo 耗时:{},src:\t {},\t target:\t{}",
(System.currentTimeMillis() - start), srcVideoPath, targetPath);
//删除图片文件
//deleteFile(imgDir);
return targetPath;
}
private static String mergeImg2Video(File videoPath, String targetFileName, String imgPath) {
try (ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor()) {
ffmpeg.addArgument("-f");
ffmpeg.addArgument("image2");
ffmpeg.addArgument("-i");
ffmpeg.addArgument(imgPath + File.separator + "image_%6d.jpg");
String video = videoPath + File.separator + targetFileName;
ffmpeg.addArgument(video);
ffmpeg.execute();
hold(ffmpeg);
return video;
} catch (Exception e) {
logger.error("mergeImg2Video error imgPath {}", imgPath, e);
}
return "";
}
public static String splitVideo2Img(String path, String target) {
try (ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor()) {
long start = System.currentTimeMillis();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(path);
ffmpeg.addArgument("-r");
ffmpeg.addArgument("10"); //提取图片频率,视频默认一秒25帧,数字越小,视频播放越快
ffmpeg.addArgument("-f");
ffmpeg.addArgument("image2");
ffmpeg.addArgument(target + File.separator + "image_%6d.jpg");
ffmpeg.execute();
ffmpeg.getProcessExitCode();
logger.info("split2Img 耗时:{}", (System.currentTimeMillis() - start));
} catch (Exception e) {
logger.error("splitVideo2Img error ", e);
}
return target;
}
private static void hold(ProcessWrapper ffmpeg) throws Exception {
try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
blockFfmpeg(br);
}
}
private static void replaceImgFile(List<File> imgList, int x, int y, int width, int height) {
long start = System.currentTimeMillis();
logger.info("替换文件开始 {}", start);
imgList.stream().map(file ->
CompletableFuture.runAsync(() ->
drawImg(file, x, y, width, height)
)).collect(Collectors.toList()).stream()
.map(CompletableFuture::join).collect(Collectors.toList());
logger.info("替换文件结束 ,共耗时:{}", (System.currentTimeMillis() - start));
}
/**
* 获取根目录下的所有文件
*
* @param imgRoot 根目录
* @return 所有文件
*/
private static List<File> getImgList(File imgRoot) {
File[] files = imgRoot.listFiles();
return Arrays.stream(files).collect(Collectors.toList());
}
/**
* 删除文件
*
* @param file 要删除的文件
*/
private static void deleteFile(File file) {
//删除图片文件
Arrays.stream(Objects.requireNonNull(file.listFiles()))
.map(f -> CompletableFuture.runAsync(f::delete))
.collect(Collectors.toList()).stream()
.map(CompletableFuture::join).collect(Collectors.toList());
file.delete();
}
/**
* 对图片文件 画矩形标注
*
* @param file 图片文件
* @param x 位置x
* @param y 位置y
* @param width 标注宽度
* @param height 标注高度
*/
public static void drawImg(File file, int x, int y, int width, int height) {
try {
BufferedImage image = ImageIO.read(file);
Graphics graphics = image.getGraphics();
graphics.setColor(Color.RED);
graphics.drawRect(x, y, width, height);
FileOutputStream outputStream = new FileOutputStream(file);
ImageIO.write(image, "jpeg", outputStream);
outputStream.close();
} catch (Exception e) {
logger.error("drawImg error ", e);
}
}
public static void main(String[] args) {
String path = "D:\\WorkSpace\\video\\src\\b.mp4";
String result = generateTagVideo(path);
System.out.println(result);
}
}
参考