java使用ffmpeg 实现图片、音频、视频添加水印功能(windows、linux)
一. windows使用
1. 安装包
链接:https://pan.baidu.com/s/1igxky8Wxhd5wFRLN9_YSgA
提取码:0qo8
ffmpeg-2021-12-12-git-996b13fac4-full_build.7z 解压
bin路径下:
2. 工具类
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; /** * @ClassName: ResultStreamHandler * @Description * @Author: 王志亭 * @Date: 2021-12-16 16:31 * @Version 1.0 **/ public class ResultStreamHandler implements Runnable{ private InputStream inputStream; ResultStreamHandler(InputStream inputStream ) { this.inputStream = inputStream; } public void run() { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader( new InputStreamReader( inputStream,"utf-8" ) ); String line = null; while ( ( line = bufferedReader.readLine() ) != null ) { System.out.println( line ); } } catch ( Throwable t ) { } finally { try { bufferedReader.close(); } catch ( IOException e ) { } } } /** * 处理process输出流和错误流,防止进程阻塞 * 在process.waitFor();前调用 * @param process */ public static void dealStream(Process process) { if (process == null) { return; } // 处理InputStream的线程 new Thread() { @Override public void run() { System.out.println("run 111111111"); BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = null; try { while ((line = in.readLine()) != null) { // logger.info("output: " + line); } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start(); // 处理ErrorStream的线程 new Thread() { @Override public void run() { System.out.println("run 222222222"); BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream())); String line = null; try { while ((line = err.readLine()) != null) { // logger.info("err: " + line); } } catch (IOException e) { e.printStackTrace(); } finally { try { err.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start(); } }
public interface CmdOutputGetter { public void dealLine(String str); }
import lombok.extern.slf4j.Slf4j; import java.io.BufferedReader; import java.io.InputStreamReader; /** * @ClassName: CmdExecutor * @Description FFmpeg 脚本命令执行器 * @Author: 王志亭 * @Date: 2021-12-18 15:17 * @Version 1.0 **/ @Slf4j public class CmdExecutor { /** * CMD操作 * @param getter 获取控制台打印信息 * @param cmd 命令 */ public static void exec(CmdOutputGetter getter, String... cmd) { try { // 创建新线程 ProcessBuilder builder = new ProcessBuilder(); // 执行命令 builder.command(cmd); builder.redirectErrorStream(true); Process proc = builder.start(); BufferedReader stdout = new BufferedReader(new InputStreamReader(proc.getInputStream())); String line; while ((line = stdout.readLine()) != null) { if (getter != null) getter.dealLine(line); } proc.waitFor(); stdout.close(); } catch (Exception e) { log.error(e.getMessage(), e); } } }
import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @ClassName: FFmpegUtil * @Description ffmpeg 工具类 * @Author: 王志亭 * @Date: 2022-04-09 15:09 * @Version 1.0 **/ @Slf4j @Service("ffmpegUtil") public class FFmpegUtil { @Value("${ffmpeg.file}") private String FFMPEG_FILE; // 视频水印文件路径 @Value("${ffmpeg.video.water.file}") private String VIDEO_WATER_FILE; // 音频水印文件路径 @Value("${ffmpeg.audio.water.file}") private String AUDIO_WATER_FILE; // 图片水印 @Value("${ffmpeg.images.water.file}") private String IMAGES_WATER_FILE; /** * @Author: 王志亭 * @Description: 图片添加图片水印 * scale=180:136 水印文件宽高 * 图片水印的矩阵坐标设置 * “overlay=10:main_h-overlay_h-10"; //左下 * * "overlay=main_w/2-overlay_w/2:main_h-overlay_h-10"; //正下 * * "overlay=main_w-overlay_w-10:main_h-overlay_h-10"; //右下 * * "overlay=10:main_h/2-overlay_h/2"; //中左 * * "overlay=main_w/2-overlay_w/2:main_h/2-overlay_h/2"; //正中 * * "overlay=main_w-overlay_w-10:main_h/2-overlay_h/2"; //中右 * * "overlay=10:10"; //左上 * * "overlay=main_w/2-overlay_w/2:10"; //正上 * * "overlay=main_w-overlay_w-10:10"; //右上 * @Date: 2022-4-13 15:03 * @return void **/ public boolean imagesShuiyin(String imagesFile, String imagesWaterFile){ String cmd = FFMPEG_FILE + " -i " + addQuotes(imagesFile) + " -vf \"movie=" + IMAGES_WATER_FILE + ",scale=180:136[watermask];[in][watermask] overlay=main_w-overlay_w-10:10[out]\" -y " + addQuotes(imagesWaterFile); // String cmd = FFMPEG_FILE + " -i " + addQuotes(imagesFile) + " -vf \"movie=C\\\\:/waterfile.png,scale=180:136[watermask];[in][watermask] overlay=main_w-overlay_w-10:10[out]\" -y " + addQuotes(imagesWaterFile); System.out.println("cmd *********************** " + cmd); boolean flag = generalCommand(cmd); return flag; } /** * @Author: 王志亭 * @Description: 音频添加水印 新版本 * @Date: 2022-4-9 17:52 * @param audioFile: 源音频文件 * @param audioWaterFile: 添加水印后的音频文件 * @return void **/ public boolean audioShuiYinNew(String audioFile, String audioWaterFile){ String cmd = FFMPEG_FILE + " -i " + addQuotes(audioFile) + " -i " + addQuotes(AUDIO_WATER_FILE) + " -filter_complex \"[0:0] [1:0] concat=n=2:v=0:a=1 [a]\" -map \"[a]\" \"-ar\" 48000 -y " + addQuotes(audioWaterFile); boolean flag = generalCommand(cmd); return flag; } /** * @Author: 王志亭 * @Description: 音频添加水印 == 旧版 * @Date: 2022-4-9 17:14 * @param audioFile: 源音频文件 * @param audioWaterFile: 添加水印后的音频文件 * @return void **/ public void audioShuiYinOld(String audioFile, String audioWaterFile){ String[] command4concat = {FFMPEG_FILE, " -i ", AUDIO_WATER_FILE.replaceAll(" ","\" \""), "-i", audioFile.replaceAll(" ","\" \""), "-filter_complex", "[0:0] [1:0] concat=n=2:v=0:a=1 [a]", "-map", "[a]", "-ar", "48000", audioWaterFile, "-y"}; CmdExecutor.exec(new CmdOutputGetter() { @Override public void dealLine(String line) { //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式 System.out.println(line); } }, command4concat); log.info("水印连接「{}秒」间隔音频完成!"); } /** * @Author: 王志亭 * @Description: 视频 添加水印 * @Date: 2021-12-19 * @param input_path: 源文件路径 * @param video_converted_path: 添加水印后的视频路径 * @return void **/ public boolean videoShuiYin(String input_path, String video_converted_path) { HashMap<String, String> dto=new HashMap<String, String>(); dto.put("ffmpeg_path",FFMPEG_FILE);//必填:此处是ffmpeg.exe所在位置,也就FFmpeg文件夹bin目录下的ffmpeg.exe dto.put("input_path", input_path);//必填;此处是你要处理的视频位置 dto.put("video_converted_path", video_converted_path);//必填;此处是完成添加水印后输入视频的位置并重新命名该视频 dto.put("logo", VIDEO_WATER_FILE);//必填;此处是你要添加的水印位置,注意此处图片位置一定要加上转译符,否则识别不了盘符 boolean flag = execute(dto); return flag; } /** * @Author: 王志亭 * @Description: 视频添加水印 * @Date: 2021-12-23 17:37 * @param dto: * @return void **/ private boolean execute(Map<String,String> dto) { StringBuffer waterlogo = new StringBuffer(); waterlogo.append(" -i "); if(null!=dto.get("input_path")&& org.apache.commons.lang.StringUtils.isNotEmpty(dto.get("input_path"))){ waterlogo.append(addQuotes(dto.get("input_path"))); } waterlogo.append(" -vf \"movie="); String xaxis = "main_w-overlay_w-10"; String yaxis = "10"; if (null!=dto.get("logo")&& org.apache.commons.lang.StringUtils.isNotEmpty(dto.get("logo"))){ waterlogo.append(dto.get("logo")); } waterlogo.append(",scale= 60: 30"); waterlogo.append(" [watermark]; [in][watermark] overlay=main_w-overlay_w-5:5 [out]\" -y "); if (null!=dto.get("video_converted_path")&& org.apache.commons.lang.StringUtils.isNotEmpty(dto.get("video_converted_path"))){ waterlogo.append(addQuotes(dto.get("video_converted_path"))); } String ffmegPath = null; if (StringUtils.isNotEmpty(dto.get("ffmpeg_path"))){ ffmegPath = dto.get("ffmpeg_path"); } System.out.println("ffmegPath+waterlogo ===== " + ffmegPath+waterlogo); boolean flag = generalCommand(ffmegPath + waterlogo); return flag; } /** * @Author: 王志亭 * @Description: 文件路径出现空格 处理方式 * @Date: 2021-12-23 16:05 * @param in: 文件路径 * @return java.lang.String **/ public static String addQuotes(String in ) { return "\"" + in + "\""; } /** * @Author: 王志亭 * @Description: 通用命令 * @Date: 2021-12-23 17:35 * @param command: ffmpeg 命令 * @return void **/ public boolean generalCommand(String command){ boolean flag = false; Runtime run = Runtime.getRuntime(); InputStream inputStream = null; InputStream errorStream = null; try { java.lang.Process process = run.exec(command); // 异步读取输出 inputStream = process.getInputStream(); errorStream = process.getErrorStream(); ExecutorService service = Executors.newFixedThreadPool(2); ResultStreamHandler inputStreamHandler = new ResultStreamHandler(inputStream); ResultStreamHandler errorStreamHandler = new ResultStreamHandler(errorStream); service.execute(inputStreamHandler); service.execute(errorStreamHandler); new Thread(errorStreamHandler, "error stream").start(); new Thread(inputStreamHandler, "output stream").start(); process.waitFor(); service.shutdownNow(); flag = true; } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); }finally { try { errorStream.close(); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return flag; } }
3. application.yml 配置文件中添加 ffmpeg
# ffmpeg 路径 ffmpeg: file: E:\wangzhiting\FFmpeg\bin\ffmpeg.exe # 视频水印文件路径 video: water: file: C\\:/water.png # 音频水印文件路径 audio: water: file: E:\音频水印.mp3 # 图片水印文件路径 注意:路径 两个反斜杠必须添加 images: water: file: C\\:/waterfile.png
* 注意:在windows下使用图片、视频文件添加图片水印时 盘符路径下必须添加两个反斜杠(\\)例如:C\\:/
4. 注意事项
(1)水印添加完的文件自动生成,ffmpeg命令执行之前生成文件会报错。
例如:如下代码
audioWaterFile:c:/file/1238795.jpg 中
1238795.jpg 文件是自动生成的,
c:/file/ 必须事先生成,不然会报文件或文件夹不存在
public boolean audioShuiYinNew(String audioFile, String audioWaterFile){ String cmd = FFMPEG_FILE + " -i " + addQuotes(audioFile) + " -i " + addQuotes(AUDIO_WATER_FILE) + " -filter_complex \"[0:0] [1:0] concat=n=2:v=0:a=1 [a]\" -map \"[a]\" \"-ar\" 48000 -y " + addQuotes(audioWaterFile); boolean flag = generalCommand(cmd); return flag; }
解决:可以添加 -y 解决文件
-y: 覆盖文件
二. linux 使用
1. 安装包
链接:https://pan.baidu.com/s/1PshVBEZ4W4eDqEXxsAw2uw 提取码:jr51
ffmpeg-4.4.tar.gz 解压后的文件
前期准备: 在/usr/local/下新建一个ffmpeg的文件夹,并进入到ffmpeg文件夹; 安装gcc(yum install -y gcc)工具; 安装wget(yum -y install wget)工具; 安装(vim)yum install -y vim;
安装ffmpeg ①:将ffmpeg-4.1.3.tar.bz2上传到ffmpeg文件夹下 ②:tar -xjvf ffmpeg-4.1.3.tar.bz2 ③: cd ffmpeg-4.1.3 ④:./configure --enable-shared --prefix=/monchickey/ffmpeg ⑤:make(花费时间会有点长) ⑥:make install ffmpeg安装完毕
ffmpeg文件
查看一下发现有bin、include、lib、share这4个目录,其中bin是ffmpeg主程序二进制目录,include是C/C++头文件目录,lib是编译好的库文件目录,share是文档目录
配置ffmpeg的环境变量
①:完成以上步奏之后,还不能直接在linux任何位置使用ffmpeg命令,因为还没有配置ffmpeg环境变量,所以这里在原文基础上,新增环境变量这一步;
②:切换root用户,执行命令:"vim /etc/profile";在文件末尾加上下面的命令行:
#set my ffmpeg environment
export PATH=$PATH:/monchickey/ffmpeg/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/monchickey/ffmpeg/lib
③:然后执行生效命令:"source /etc/profile";
④:最后测试一下是否已经生效,在随便一个路径下执行命令:"ffmpeg -version";出现以下版本信息,说明ffmpeg环境变量配置成功,可直接在linux使用命令调用ffmpeg程序;
2. 使用方法跟windows不同的地方
(1)application.yml ffmpeg 配置
# ffmpeg 路径 ffmpeg: file: /usr/local/ffmpeg/bin/ffmpeg # 视频水印文件路径 video: water: file: /usr/local/tomcat-news-server/water.png audio: water: file: /usr/local/tomcat-news-server/音频水印.mp3 images: water: file: /usr/local/tomcat-news-server/waterfile.png
(2)FFmpegUtil 工具类中的 public boolean generalCommand(String command) 方法
windows 下的是:
linux 下 是:
FFmpegUtil linux 下使用 工具类
* 本人汉语水平不好,如果有些描述不明确或者不明白的可以联系我 QQ:905352007 请备注:FFMPEG