目地:写一个java制作视频的网站。
大致流程:用户上传n张图片和背景音乐,网站返回一个视频。
大家都知道,java是通过命令行来调用ffmpeg的,java没有现成的比较好的封装ffmpeg的jar包。
所以,我们需要自己写调用ffmpeg的接口。
我采用的是spring boot架构。
首先配置pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--configuration-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--start:视频获取某一帧的图片-->
<!--<dependency>-->
<!--<groupId>org.bytedeco</groupId>-->
<!--<artifactId>javacv</artifactId>-->
<!--<version>1.4.3</version>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.bytedeco.javacpp-presets</groupId>-->
<!--<artifactId>ffmpeg-platform</artifactId>-->
<!--<version>4.0.2-1.4.3</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<!--end:视频获取某一帧的图片-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!--导入jsp相关包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- jsp 依赖的jar包start-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
</dependencies>
注意:整个项目全部依赖此pom文件,后面不再更改。
我们还需要一个命令行处理器,创建一个CmdExecuter
package com.example.demo.util;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
public class CmdExecuter {
public static String exec(List<String> cmd) {
String converted_time = null;
Process proc =null;
BufferedReader stdout = null;
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(cmd);
builder.redirectErrorStream(true);
proc = builder.start();
stdout = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line;
int lineNumber=1;
List<String> returnStringList = new LinkedList<String>();
// 命令行信息
for(String attribute : cmd) {
System.out.println(attribute);
}
// 命令行调试信息
while ((line = stdout.readLine()) != null) {
System.out.println("第"+lineNumber+"行:"+line);
lineNumber=lineNumber+1;
returnStringList.add(FFMPEG.dealString(line));
}
String info = "";
for (int i = returnStringList.size() - 1; i >= 0; i--) {
if (null != returnStringList.get(i) && returnStringList.get(i).startsWith("frame=")) {
info = returnStringList.get(i);
break;
}
}
if (null != info) {
converted_time = info.split("time=")[1].split("bitrate=")[0].trim();
}
} catch (IndexOutOfBoundsException ex) {
converted_time = null;
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
proc.waitFor();
stdout.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return converted_time;
}
}
随后我们需要一个配置地址的类PropertyPlaceholder
package com.example.demo.entity;
import org.springframework.stereotype.Component;
/**
* @program: javavc
* @author: wlr
* @create: 2019-07-26 10:20
* @description:
**/
@Component
//@ConfigurationProperties(prefix = "person")
public class PropertyPlaceholder {
// 根目录
static String rootdir=System.getProperty("user.dir");
// ffmpeg路径
private String ffmpegpath="\\src\\main\\java\\com\\example\\demo\\tool\\bin\\ffmpeg.exe";
// 图片输入目录
private String pictureInput="\\src\\main\\resources\\picture\\input";
// 图片输出目录
private String pictureOut="\\src\\main\\resources\\picture\\out";
// 视频输入目录
private String mp4Input="\\src\\main\\resources\\mp4\\input";
// 视频输出目录
private String mp4Out="\\src\\main\\resources\\mp4\\out";
// private String videoPath=mp4Input;
// private String videoFramesPath=mp4Out;
public static String getRootdir() {
return rootdir;
}
public String getFfmpegpath() {
return rootdir+ffmpegpath;
}
public void setFfmpegpath(String ffmpegpath) {
this.ffmpegpath = ffmpegpath;
}
public String getPictureInput() {
return rootdir+pictureInput;
}
public void setPictureInput(String pictureInput) {
this.pictureInput = pictureInput;
}
public String getPictureOut() {
return rootdir+pictureOut;
}
public void setPictureOut(String pictureOut) {
this.pictureOut = pictureOut;
}
public String getMp4Input() {
return rootdir+mp4Input;
}
public void setMp4Input(String mp4Input) {
this.mp4Input = mp4Input;
}
public String getMp4Out() {
return rootdir+mp4Out;
}
public void setMp4Out(String mp4Out) {
this.mp4Out = mp4Out;
}
// public String getVideoPath() {
// return rootdir+videoPath;
// }
//
// public void setVideoPath(String videoPath) {
// this.videoPath = videoPath;
// }
//
// public String getVideoFramesPath() {
// return rootdir+videoFramesPath;
// }
//
// public void setVideoFramesPath(String videoFramesPath) {
// this.videoFramesPath = videoFramesPath;
// }
public String getPictureOut(String img_path) {
return rootdir+pictureOut+"\\"+img_path;
}
}
接下来我们需要在PropertyPlaceholder类指向ffmpeg的地址放放置ffmpeg的解压版
下载地址:http://www.ffmpeg.org/download.html
新建一个FFMPEG,通过命令行调用ffmpeg.exe。
package com.example.demo.util;
import com.example.demo.entity.PropertyPlaceholder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@Component
public class FFMPEG {
@Autowired
PropertyPlaceholder propertyPlaceholder;
public static String dealString(String str) {
Matcher m = java.util.regex.Pattern.compile("^frame=.*").matcher(str);
String msg = "";
while (m.find()) {
msg = m.group();
}
return msg;
}
/**
* 如果是数字就是成功的时间(秒数)
*
* @param str
* @return
*/
public static boolean isNumeric(String str) {
Pattern pattern = Pattern.compile("[0-9]*");
Matcher isNum = pattern.matcher(str);
if (!isNum.matches()) {
return false;
}
return true;
}
/**
* 如果返回不是null的值就是成功(值为转换用时单位:秒)
*
* @param instr
* @return
*/
public static String returnSecond(String instr) {
String returnValue = null;
if (null != instr) {
String[] a = instr.split("\\.");
String[] b = a[0].split(":");
// 返回秒
int returnNumber = 0;
if (null != instr && b[0].length() != 0) {
returnNumber = Integer.valueOf(b[0]) * 60 * 60 + Integer.valueOf(b[1]) * 60 + Integer.valueOf(b[2]);
returnValue = String.valueOf(returnNumber);
} else {
returnValue = null;
}
}
return returnValue;
}
/**
* 获取视频格式(转码前的格式和转码后的格式都可以调用)
*
* @param outputPath
* @return
*/
public static String returnVideoFormat(String outputPath) {
return outputPath.substring(outputPath.lastIndexOf(".") + 1);
}
public String jpgTomp4(HashMap<String, String> dto){
List<String> cmd = new ArrayList<String>();
cmd.add(dto.get("ffmpeg_path"));
cmd.add("-y");
cmd.add("-i");
cmd.add(dto.get("input_path"));
if (null != dto.get("screen_size")) {
cmd.add("-s");
cmd.add(dto.get("screen_size"));
}
if (null != dto.get("logo")) {
String logo = dto.get("logo");
cmd.add("-vf");
String xaxis = dto.get("xaxis");
String yaxis = dto.get("yaxis");
xaxis = xaxis != null && !xaxis.equals("") ? xaxis : "0";
yaxis = yaxis != null && !yaxis.equals("") ? yaxis : "0";
String logoString = "movie=" + logo + "[logo],[in][logo]overlay=x=" + xaxis + ":y=" + yaxis + "[out]";
cmd.add(logoString);
}
cmd.add("-strict");
cmd.add("-2");
if (null != dto.get("vb") && !dto.get("vb").equals("")) {
cmd.add("-vb");
cmd.add(dto.get("vb") + "k");
}
if (null != dto.get("ab") && !dto.get("ab").equals("")) {
cmd.add("-ab");
cmd.add(dto.get("ab") + "k");
}
cmd.add("-q:v");
cmd.add("4");
cmd.add(dto.get("video_converted_path"));
String converted_time = CmdExecuter.exec(cmd);
return returnSecond(converted_time);//获取转换时间
}
/**
* @ HashMap<String,String> dto 参数传递对象<br>
* dto中包含的参数<br>
* (必填)1.ffmpeg_path:ffmpeg执行文件地址,如 d:\\ffmpeg\\ffmpeg.exe Linux下直接调用ffmpeg命令(当然你事先已经有这个程序了)<br>
* (必填)2.input_path:原视频路径<br>
* (必填)3.video_converted_path:转换后视频输出路径<br>
* (可选)4.screen_size:视频尺寸 长度乘宽度 乘号用英文小写"x"如 512x480<br>
* (可选)5.logo:水印地址(其实在ffmpeg中有一个专门的watermark参数,logo跟它有何不同,我还没看,不过对我来说效果一样 貌似需要png图片才行)<br>
* (可选,如果填写必须有logo才行,默认为0)6.xaxis:水印logo的横坐标(只有logo参数为一个正确路径才行) 比如0<br>
* (可选,如果填写必须有logo才行,默认为0)6.yaxis:水印logo的纵坐标(只有logo参数为一个正确路径才行) 比如0<br>
* (可选)vb:视频比特率,传入一个数值,单位在程序里面拼接了k
* (可选)ab:音频比特率,传入一个数值,单位在程序里面拼接了k
*/
public String videoTransfer(HashMap<String, String> dto) {
// String ffmpeg_path,String input_path,String video_converted_path,String logo,String screen_size,String xaxis,String yaxis,String vb,String ab
List<String> cmd = new ArrayList<String>();
cmd.add(dto.get("ffmpeg_path"));
cmd.add("-y");
cmd.add("-i");
cmd.add(dto.get("input_path"));
if (null != dto.get("screen_size")) {
cmd.add("-s");
cmd.add(dto.get("screen_size"));
}
if (null != dto.get("logo")) {
String logo = dto.get("logo");
cmd.add("-vf");
String xaxis = dto.get("xaxis");
String yaxis = dto.get("yaxis");
xaxis = xaxis != null && !xaxis.equals("") ? xaxis : "0";
yaxis = yaxis != null && !yaxis.equals("") ? yaxis : "0";
String logoString = "movie=" + logo + "[logo],[in][logo]overlay=x=" + xaxis + ":y=" + yaxis + "[out]";
cmd.add(logoString);
}
cmd.add("-strict");
cmd.add("-2");
if (null != dto.get("vb") && !dto.get("vb").equals("")) {
cmd.add("-vb");
cmd.add(dto.get("vb") + "k");
}
if (null != dto.get("ab") && !dto.get("ab").equals("")) {
cmd.add("-ab");
cmd.add(dto.get("ab") + "k");
}
cmd.add("-q:v");
cmd.add("4");
cmd.add(dto.get("video_converted_path"));
String converted_time = CmdExecuter.exec(cmd);
return returnSecond(converted_time);//获取转换时间
}
/*
* 给一张图片添加图片水印
* */
public void makeWatermask(String inputImage0,String inputImage1,int width,int height,int x,int y,String outputPath){
List<String> cmd = new ArrayList<String>();
cmd.add(propertyPlaceholder.getFfmpegpath());
cmd.add("-i");
cmd.add(inputImage0);
cmd.add("-vf");
cmd.add("movie='"+inputImage1.replace("\\","\\\\").replace(":","\\:")+"',"+"scale="+width+":"+height+"[watermask];[in][watermask]"+"overlay="+x+":"+y+"[out]");
cmd.add("-y");
cmd.add(outputPath);
CmdExecuter.exec(cmd);
}
/*
* 给1张图片添加2张图片水印
* */
public void makeWatermask(String inputImage0,String inputImage1,int width,int height,int x,int y,String inputImage2,int width1,int height1,int x1,int y1,String outputPath){
List<String> cmd = new ArrayList<String>();
cmd.add(propertyPlaceholder.getFfmpegpath());
cmd.add("-i");
cmd.add(inputImage0);
cmd.add("-vf");
cmd.add("movie='"+inputImage1.replace("\\","\\\\").replace(":","\\:")+"',"+"scale="+width+":"+height+"[1watermask];movie='"+inputImage2.replace("\\","\\\\").replace(":","\\:")+"',"+"scale="+width+":"+height+"[2watermask];[in][1watermask]"+"overlay="+x+":"+y+"[int];[int][2watermask]overlay="+x1+":"+y1+"[out]");
cmd.add("-y");
cmd.add(outputPath);
CmdExecuter.exec(cmd);
}
/**
* @param input_path 图片输入正则路径
* @param video_converted_path 视频输出路径
*/
/*
在input_path文件夹下,将1.png~n.png的图片转化为视频,注意,这里的图片必须是1.png起始
* */
public void imageToMp4(String input_path,String video_converted_path){
List<String> cmd = new ArrayList<String>();
cmd.add(propertyPlaceholder.getFfmpegpath());
cmd.add("-f");
cmd.add("image2");
cmd.add("-i");
cmd.add(input_path);
cmd.add("-vcodec");
cmd.add("libx264");
cmd.add("-r");
cmd.add("25");
cmd.add("-b");
cmd.add("382k");
cmd.add("-y");
cmd.add(video_converted_path);
CmdExecuter.exec(cmd);
}
/*
给视频添加水印
input_path:视频输入路径
video_converted_path:视频转换路径
logo:logo图片转换路径
xaxis:logo图片相对于视频的横坐标
yaxis:logo图片相对于视频的纵坐标
* */
public void addWatermark(String input_path,String video_converted_path,String logo,String xaxis,String yaxis){
HashMap<String, String> dto = new HashMap<String, String>();
dto.put("ffmpeg_path", propertyPlaceholder.getFfmpegpath());//必填
dto.put("input_path", input_path);//必填
dto.put("video_converted_path", video_converted_path);//必填
dto.put("logo", "C\\\\:/Users/WLR/Desktop/project/video_demo/src/oyuemeili.png");//可选(注意windows下面的logo地址前面要写4个反斜杠,如果用浏览器里面调用servlet并传参只用两个,如 d:\\:/ffmpeg/input/logo.png)
dto.put("xaxis",xaxis);
dto.put("yaxis",yaxis);
String secondsString = new FFMPEG().videoTransfer(dto);
System.out.println("转换共用:" + secondsString + "秒");
}
/**
* 将.MP4转化为.ts
* @param mp4Address mp4地址
* @param tsAddress ts地址
*/
public void mp4ToTs(String mp4Address, String tsAddress) {
List<String> cmd = new ArrayList<String>();
cmd.add(propertyPlaceholder.getFfmpegpath());
cmd.add("-i");
cmd.add(mp4Address);
cmd.add("-vcodec");
cmd.add("copy");
cmd.add("-vbsf");
cmd.add("h264_mp4toannexb ");
cmd.add("-y");
cmd.add(tsAddress);
CmdExecuter.exec(cmd);
}
/**
* 将多个.ts转换为.mp4
* @param tsName
* @param outTsAddress
*/
public void addTsToMP4(ArrayList<String> tsName, String outTsAddress) {
List<String> cmd = new ArrayList<String>();
cmd.add(propertyPlaceholder.getFfmpegpath());
cmd.add("-i");
int size=tsName.size();
String concat="\"concat:";
for (int i=0;i<size-1;i++){
concat=concat+(String) tsName.remove(0)+"|";
}
concat=concat+(String) tsName.remove(0)+"\"";
cmd.add(concat);
cmd.add("-c");
cmd.add("copy");
cmd.add("-bsf:a");
cmd.add("aac_adtstoasc");
cmd.add("-movflags");
cmd.add("faststart");
cmd.add("-y");
cmd.add(outTsAddress);
cmd.add("-y");
CmdExecuter.exec(cmd);
}
/**
* 给MP4视频添加背景音乐
* @param mp3Address
* @param mp4Address
*/
public void addMp3(String mp3Address,String mp4Address,String output){
List<String> cmd=new ArrayList<>();
cmd.add(propertyPlaceholder.getFfmpegpath());
cmd.add("-i");
cmd.add(mp3Address);
cmd.add("-i");
cmd.add(mp4Address);
cmd.add("-y");
cmd.add(output);
CmdExecuter.exec(cmd);
}
// public static void main(String[] arg) {
// HashMap<String, String> dto = new HashMap<String, String>();
// dto.put("ffmpeg_path", "C:\\Users\\WLR\\Desktop\\project\\ffmpeg-20190628-098ab93-win64-static\\ffmpeg-20190628-098ab93-win64-static\\bin\\ffmpeg.exe");//必填
// dto.put("input_path", "C:\\Users\\WLR\\Desktop\\project\\video_demo\\src\\瓯越文化.mp4");//必填
// dto.put("video_converted_path", "C:\\Users\\WLR\\Desktop\\project\\video_demo\\src\\aaa.mp4");//必填
// dto.put("logo", "C\\\\:/Users/WLR/Desktop/project/video_demo/src/oyuemeili.png");//可选(注意windows下面的logo地址前面要写4个反斜杠,如果用浏览器里面调用servlet并传参只用两个,如 d:\\:/ffmpeg/input/logo.png)
// dto.put("xaxis","100");
// dto.put("yaxis","100");
// String secondsString = new FFMPEG().videoTransfer(dto);
// System.out.println("转换共用:" + secondsString + "秒");
// }
}
创建一个 接口MarkText,用来调用FFMPEG工具
package com.example.demo.service;
import java.awt.*;
import java.util.ArrayList;
import java.util.Queue;
public interface MarkText {
//加文字水印
void mark(String imgPath, String outImgPath, String text, Font font, Color color, int x, int y);
//加图片水印
void mark(String inputImg, String markImg, String outputImg, int width, int height, int x, int y);
//通过gotravale视频模版在文件夹下生成视频帧的图片
void makePictureInDirByGotravale(String username, ArrayList filename);
//制作视频
void makeMp4(String imgDir,String mp3);
}
用类MarkText4J实现 接口MarkText
package com.example.demo.service.impl;
import com.example.demo.entity.PropertyPlaceholder;
import com.example.demo.service.MarkText;
import com.example.demo.util.FFMPEG;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
* @program: demo
* @author: wlr
* @create: 2019-06-29 21:28
* @description:水印图片的四种方式
**/
@Service
public class MarkText4J implements MarkText {
@Autowired
PropertyPlaceholder propertyPlaceholder;
@Autowired
FFMPEG ffmpeg;
//静态资源文件本地路径(非target)
String localAddress=propertyPlaceholder.getRootdir()+"\\src\\main\\resources";
// 加文字水印
public void mark(BufferedImage bufImg, Image img, String text, Font font, Color color, int x, int y) {
Graphics2D g = bufImg.createGraphics();
g.drawImage(img, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);
g.setColor(color);
g.setFont(font);
g.drawString(text, x, y);
g.dispose();
}
// 加图片水印
public void mark(BufferedImage bufImg, Image img, Image markImg, int width, int height, int x, int y) {
Graphics2D g = bufImg.createGraphics();
g.drawImage(img, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);
g.drawImage(markImg, x, y, width, height, null);
g.dispose();
}
/**
* 给图片增加文字水印
*
* @param imgPath
* -要添加水印的图片路径
* @param outImgPath
* -输出路径
* @param text-文字
* @param font
* -字体
* @param color
* -颜色
* @param x
* -文字位于当前图片的横坐标
* @param y
* -文字位于当前图片的竖坐标
*/
public void mark(String imgPath, String outImgPath, String text, Font font, Color color, int x, int y) {
try {
// 读取原图片信息
File imgFile = null;
Image img = null;
if (imgPath != null) {
imgFile = new File(imgPath);
}
if (imgFile != null && imgFile.exists() && imgFile.isFile() && imgFile.canRead()) {
img = ImageIO.read(imgFile);
}
int imgWidth = img.getWidth(null);
int imgHeight = img.getHeight(null);
// 加水印
BufferedImage bufImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
mark(bufImg, img, text, font, color, x, y);
// 输出图片
FileOutputStream outImgStream = new FileOutputStream(outImgPath);
ImageIO.write(bufImg, "jpg", outImgStream);
outImgStream.flush();
outImgStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 给图片增加图片水印
*
* @param inputImg
* -源图片,要添加水印的图片
* @param markImg
* - 水印图片
* @param outputImg
* -输出图片(可以是源图片)
* @param width
* - 水印图片宽度
* @param height
* -水印图片高度
* @param x
* -图片相对于原图片横坐标
* @param y
* -图片相对于原图片纵坐标
*/
public void mark(String inputImg, String markImg, String outputImg, int width, int height, int x, int y) {
// 读取原图片信息
File inputImgFile = null;
File markImgFile = null;
Image img = null;
Image mark = null;
try {
if (inputImg != null && markImg != null) {
inputImgFile = new File(inputImg);
markImgFile = new File(markImg);
}
if (inputImgFile != null && inputImgFile.exists() && inputImgFile.isFile() && inputImgFile.canRead()) {
img = ImageIO.read(inputImgFile);
}
if (markImgFile != null && markImgFile.exists() && markImgFile.isFile() && markImgFile.canRead()) {
mark = ImageIO.read(markImgFile);
}
int imgWidth = img.getWidth(null);
int imgHeight = img.getHeight(null);
BufferedImage bufImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
mark(bufImg, img, mark, width, height, x, y);
FileOutputStream outImgStream = new FileOutputStream(outputImg);
ImageIO.write(bufImg, "png", outImgStream);
outImgStream.flush();
outImgStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void makeVideoframe1Aboutgotravale(String username,String inputImg,String sep,int pictureNum){
ffmpeg.makeWatermask(localAddress+"\\moban\\gotravale\\blank.png",localAddress+"\\user\\"+username+"\\picture\\input\\"+inputImg,725,418,221,76,localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg);
// mark( localAddress+"\\moban\\gotravale\\blank.png",localAddress+"\\user\\"+username+"\\picture\\input\\"+inputImg, localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg, 725, 418, 221, 76);
makeVideoframeAboutgotravale(username,inputImg,sep,pictureNum);
}
public void makeVideoframe2Aboutgotravale(String username,String inputImg,String inputImg1,String sep,int pictureNum){
ffmpeg.makeWatermask(localAddress+"\\moban\\gotravale\\blank.png",localAddress+"\\user\\"+username+"\\picture\\input\\"+inputImg,411,399,58,38,localAddress+"\\user\\"+username+"\\picture\\input\\"+inputImg1,343,301,499,165,localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg);
// mark( localAddress+"\\moban\\gotravale\\blank.png",localAddress+"\\user\\"+username+"\\picture\\input\\"+inputImg, localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg, 411, 399, 58, 38);
// mark( localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg,localAddress+"\\user\\"+username+"\\picture\\input\\"+inputImg1, localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg, 343, 301, 499, 165);
makeVideoframeAboutgotravale(username,inputImg,sep,pictureNum);
}
public void makeVideoframeAboutgotravale(String username,String inputImg,String sep,int pictureNum){
for (int num=1;num<=pictureNum;){
ffmpeg.makeWatermask(localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg,localAddress+"\\moban\\gotravale\\"+sep+"\\"+num+".png",998,560,0,0,localAddress+"\\user\\"+username+"\\picture\\"+sep+"\\"+num+".jpg");
// mark(localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg, localAddress+"\\moban\\gotravale\\"+sep+"\\"+num+".png", localAddress+"\\user\\"+username+"\\picture\\"+sep+"\\"+num+".png", 998, 560, 0, 0);
num++;
}
}
public void makeVideoframe3Aboutgotravale(String username,String inputImg,String sep,int pictureNum){
mark( localAddress+"\\moban\\gotravale\\blank.png",localAddress+"\\user\\"+username+"\\picture\\input\\"+inputImg, localAddress+"\\user\\"+username+"\\picture\\out\\"+inputImg, 754, 442, 214, 53);
makeVideoframeAboutgotravale(username,inputImg,sep,pictureNum);
}
/**
* @param username 用户名
* @param filename 输入图片名的集合
*/
//給特定一个文件夹下的图片添加水印
@Override
public void makePictureInDirByGotravale(String username, ArrayList filename) {
/*
* 生成sep1文件夹关键帧
* */
//将用户上传的图片水印到空白图片上
String inputImg=(String) filename.remove(0);
makeVideoframe1Aboutgotravale(username,inputImg,"sep1",85);
/*
* 生成sep2文件夹关键帧
* */
inputImg=(String) filename.remove(0);
String inputImg1=(String) filename.remove(0);
makeVideoframe2Aboutgotravale(username,inputImg,inputImg1,"sep2",166);
/*
* 生成sep3文件夹关键帧
* */
inputImg=(String) filename.remove(0);
makeVideoframe3Aboutgotravale(username,inputImg,"sep3",82);
/*
将图片生成对应的MP4文件
* */
ffmpeg.imageToMp4(localAddress+"\\user\\"+username+"\\picture\\sep1\\%d.jpg",localAddress+"\\user\\"+username+"\\mp4\\input\\sep1.mp4");
ffmpeg.imageToMp4(localAddress+"\\user\\"+username+"\\picture\\sep2\\%d.jpg",localAddress+"\\user\\"+username+"\\mp4\\input\\sep2.mp4");
ffmpeg.imageToMp4(localAddress+"\\user\\"+username+"\\picture\\sep3\\%d.jpg",localAddress+"\\user\\"+username+"\\mp4\\input\\sep3.mp4");
/*
* 将mp4转化为ts文件
* */
ffmpeg.mp4ToTs(localAddress+"\\user\\"+username+"\\mp4\\input\\sep1.mp4",localAddress+"\\user\\"+username+"\\ts\\sep1.ts");
ffmpeg.mp4ToTs(localAddress+"\\user\\"+username+"\\mp4\\input\\sep2.mp4",localAddress+"\\user\\"+username+"\\ts\\sep2.ts");
ffmpeg.mp4ToTs(localAddress+"\\user\\"+username+"\\mp4\\input\\sep3.mp4",localAddress+"\\user\\"+username+"\\ts\\sep3.ts");
/*
* 将ts文件黏连在一起,转化为mp4
* */
ArrayList<String> tsName = new ArrayList<>();
tsName.add(localAddress+"\\moban\\gotravale\\ts\\start.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\start_font.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\start_end.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\sep1start.ts");
tsName.add(localAddress+"\\user\\"+username+"\\ts\\sep1.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\sep1end.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\sep2start.ts");
tsName.add(localAddress+"\\user\\"+username+"\\ts\\sep2.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\sep2end.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\sep3start.ts");
tsName.add(localAddress+"\\user\\"+username+"\\ts\\sep3.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\sep3end.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\end.ts");
tsName.add(localAddress+"\\moban\\gotravale\\ts\\end_font.ts");
ffmpeg.addTsToMP4(tsName,localAddress+"\\user\\"+username+"\\mp4\\input\\input.mp4");
/*
* 给MP4视频添加背景音乐
* */
ffmpeg.addMp3(localAddress+"\\user\\"+username+"\\mp3\\"+filename.remove(0),localAddress+"\\user\\"+username+"\\mp4\\input\\input.mp4",localAddress+"\\user\\"+username+"\\mp4\\out\\output.mp4");
}
@Override
public void makeMp4(String imgDir, String mp3) {
// ffmpeg.imageToMp4(imgDir,);
}
}
最后,通过VideoController调用MarkText4J
package com.example.demo.controller;
import com.example.demo.service.impl.MarkText4J;
//import com.example.demo.util.VideoProcessing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Queue;
@RestController
public class VideoController {
@Autowired
MarkText4J mark;
// 将视频用图片的形式保存
// @RequestMapping(value = "/grabberVideoFrame")
// public void grabberVideoFrame(String videoFileName){
// videoProcessing.grabberVideoFramer(videoFileName);
// }
// 将图片生成视频
@RequestMapping(value = "/makeVideo")
public void makeVideo(){
ArrayList<String> filename = new ArrayList<>();
filename.add("1.png");
filename.add("2.png");
filename.add("3.png");
filename.add("4.png");
filename.add("culture.mp3");
mark.makePictureInDirByGotravale("www",filename);
}
}
以下是我的文件放置方式,方便大家实现demo ,
application.properties
#环境为springboot2.0.6
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
spring.servlet.multipart.resolve-lazily=false
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
spring.jpa.open-in-view=false
application.yml
spring:
#配置属性文件
profiles:
active: dev
# 配置默认访问路径
resources:
static-locations: classpath:/
#配置MySQL
datasource:
#添加驱动器名称
driver-class-name: com.mysql.cj.jdbc.Driver
#添加数据库连接的url
url: jdbc:mysql://127.0.0.1:3306/pyt?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
#配置数据库用户名
username:
#配置数据库密码
password:
#配置jpa
jpa:
hibernate:
#ddl-auto属性:
# create 启动时删数据库中的表,然后创建,退出时不删除数据表
# create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
# update 如果启动时表格式不一致则更新表,原有数据保留
# validate 项目启动表结构进行校验 如果不一致则报错
ddl-auto: update
#改属性值若为true,操作数据库时,可打印SQL语句到控制台
show-sql: true
由于图片,视频等资源过大,我将其上传到我的博客中大家可以自行下载。目前在intel i7 7700的cpu下需要40秒左右生成一个4.76MB的视频,项目算法正在优化中。
在下一篇文章中,将会阐述视频制作逻辑