用户在登录之后可以在个人信息页面点击上传视频按钮,会让用户在本地选择一段视频进行上传,视频不能过长,选择好后,用户会跳转到选择背景音乐的界面,可以选择为该视频加上一段背景音乐,并且可以对该视频做相关描述,然后点击上传视频按钮,完成视频上传。
用户在上传视频之前需要查询后台所有的bgm,并显示出来,该接口需要将数据库中所有的bgm查询到,并返回给前端
dao层
public interface BgmDao extends JpaRepository {
}
service层
/**
* 查询全部的bgm并返回
* @return
*/
@Override
public List findAllBgm() {
return bgmDao.findAll();
}
controller层
@Autowired
private BgmService bgmService;
@ApiOperation(value = "查询BGM列表", notes = "查询bgm列表的接口")
@PostMapping("/bgmList")
public LexJSONResult bgmList(){
//查询全部都bgm列表
List allBgm = bgmService.findAllBgm();
return LexJSONResult.ok(allBgm);
}
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
使用该技术主要是为了处理音频与视频的结合,以及生成视频封面
FfmPeg主要是通过cmd命令对文件进行相关操作的
ffmpeg.exe -i test.mp4 -i bgm.mp3 -t 3 -y new.mp4
该命令的含义为将test.mp4和bgm.mp3合成为一个新的mp4文件,且长度为3s,使用该命令就可以将我们的音频和视频进行合成的处理了
ffmpeg.exe -ss 00:00:02 -y -i lex.mp4 -vframes 1 lex.jpg
改命令表示把lex.mp4这个视频的第二秒的内容进行一个截图,并保存为lex.jpg,使用该命令就可以生成视频的封面图
首先我们如果想要使用FfmPeg对视频进行相关操作就一定要用到cmd命令,所以我们需要使用java中相关的类实现对cmd命令的操作
/**
* 该类为对音频视频进行合成处理的工具类
*/
public class FFMPeg {
private String ffmpegEXE;
public FFMPeg(String ffmpegEXE) {
this.ffmpegEXE = ffmpegEXE;
}
public void convert(String in,String mp3,Integer seconds,String out) throws IOException {
//ffmpeg.exe -i test.mp4 -i bgm.mp3 -t 3 -y new.mp4
//把一段命令通过空格的方式分割,并存入list
List cmd=new ArrayList();
cmd.add(ffmpegEXE);
cmd.add("-i");
cmd.add(in);
cmd.add("-i");
cmd.add(mp3);
cmd.add("-t");
cmd.add(String.valueOf(seconds));
cmd.add("-y");
cmd.add(out);
//该类为java中操作cmd命令的一个类,参数中需要传入一个list列表
ProcessBuilder processBuilder =new ProcessBuilder(cmd);
Process process = processBuilder.start();
//下面的操作是对FFMPeg中错误流的处理,如果不做该处理,最后合成的视频会不完整且无法播放
InputStream errorStream = process.getErrorStream();
InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
BufferedReader br = new BufferedReader(inputStreamReader);
String line = "";
while ( (line = br.readLine()) != null ) {
}
if (br != null) {
br.close();
}
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (errorStream != null) {
errorStream.close();
}
}
}
/**
* 该类用于生成上传视频的封面图
*/
public class FFMPegCover {
private String ffmpegEXE;
public FFMPegCover(String ffmpegEXE) {
this.ffmpegEXE = ffmpegEXE;
}
public void convert(String in,String out) throws IOException {
//ffmpeg.exe -ss 00:00:01 -y -i lex.mp4 -vframes 1 lex.jpg
List cmd=new ArrayList();
cmd.add(ffmpegEXE);
cmd.add("-ss");
cmd.add("00:00:01");
cmd.add("-y");
cmd.add("-i");
cmd.add(in);
cmd.add("-vframes");
cmd.add("1");
cmd.add(out);
for (String s:cmd
) {
System.out.print(s);
}
ProcessBuilder processBuilder =new ProcessBuilder(cmd);
Process process = processBuilder.start();
InputStream errorStream = process.getErrorStream();
InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
BufferedReader br = new BufferedReader(inputStreamReader);
String line = "";
while ( (line = br.readLine()) != null ) {
}
if (br != null) {
br.close();
}
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (errorStream != null) {
errorStream.close();
}
}
public static void main(String[] args) {
FFMPegCover ffmPegCover = new FFMPegCover("F:\\ffmpeg\\bin\\ffmpeg.exe");
try {
ffmPegCover.convert("F:\\ffmpeg\\bin\\lex.mp4","F:\\ffmpeg\\bin\\lex.jpg");
} catch (IOException e) {
e.printStackTrace();
}
}
}
需要前端传递的参数有用户的id,bgm的id视频的长度,视频的宽度,视频的时长,上传文件的二进制文件 ,和上传头像的步骤相似,不过在传入参数时需要注意不要传递对象,否则会出现文件无法识别的的错误报415异常
controller层代码
@Autowired
private BgmService bgmService;
@Autowired
private VideosService videosService;
//该变量为ffmpeg所在的路径
private final String FFMPEGEXE="F:\\ffmpeg\\bin\\ffmpeg.exe";
//用户资源文件的目录
private final String FILESPACE="F:/file";
//默认每页显示数据数
private final Integer SIZE=4;
/**
*
* @param id
* @param bgmId
* @param videoSeconds
* @param videoWidth
* @param videoHeight
* @param desc
* @param file
* @return
* @throws IOException
*/
@ApiOperation(value = "用户视频上传", notes = "用户视频上传的接口")
@PostMapping(value = "/uploadVideo",headers = "content-type=multipart/form-data")
public LexJSONResult upload(@RequestParam("id") String id,
String bgmId,
@RequestParam("videoSeconds") Integer videoSeconds,
@RequestParam("videoWidth") int videoWidth,
@RequestParam("videoHeight") int videoHeight,
String desc,
@ApiParam(value = "短视频",required = true) MultipartFile file) throws IOException {
if (StringUtils.isEmpty(id)){
return LexJSONResult.errorMsg("id不能为空");
}
//文件保存的命名空间
FileOutputStream fileOutputStream=null;
InputStream inputStream=null;
//定义视频存放的相对路径
String upLoadPathDb="/"+id+"/video";
//定义视频封面存放的相路径
String coverPathDb="/"+id+"/video";
String coverName="/"+UUID.randomUUID().toString()+".jpg";
try {
if (file!=null){
String filename = file.getOriginalFilename();
if (!StringUtils.isEmpty(filename)){
String finalPath=FILESPACE+upLoadPathDb+"/"+filename;
upLoadPathDb=upLoadPathDb+"/"+filename;
File f=new File(finalPath);
if (f.getParentFile()!=null || !f.getParentFile().isDirectory()){
f.getParentFile().mkdirs();
}
fileOutputStream=new FileOutputStream(f);
inputStream=file.getInputStream();
IOUtils.copy(inputStream,fileOutputStream);
if (!StringUtils.isEmpty(bgmId)){
//判断bgmid是否为空,如果不为空,说明用户选择了bgm,根据该bgmid对bgm进行查询
Bgm bgm = bgmService.findOne(bgmId);
//获取bgm的相对路径,并和命名空间拼接获得该bgm的绝对路径
String bgmPath = FILESPACE+ bgm.getPath();
//创建ffmpeg工具类,并传入ffmpeg.exe所在的路径
FFMPeg ffmPeg =new FFMPeg(FFMPEGEXE);
//该路径为视频合成后需要存放的相对路径
upLoadPathDb="/"+id+"/video"+"/"+UUID.randomUUID()+".mp4";
//使用uuid生成新得mp4视频名称并且和路径拼接,作为绝对路径传入ffmpeg的convert方法中
String videoOutPath=FILESPACE+upLoadPathDb;
//分别传入用户上传视频的绝对路径bgm的路径,视频的秒数,合成后视频的路径
ffmPeg.convert(finalPath,bgmPath,videoSeconds,videoOutPath);
}
FFMPegCover ffmPegCover =new FFMPegCover(FFMPEGEXE);
ffmPegCover.convert(FILESPACE+upLoadPathDb,FILESPACE+coverPathDb+coverName);
//TODO 保存视频到数据库
Videos videos =new Videos();
videos.setUserId(id);
videos.setAudioId(bgmId);
videos.setVideoDesc(desc);
videos.setStatus(1);
videos.setVideoSeconds((float)videoSeconds);
videos.setVideoHeight(videoHeight);
videos.setVideoWidth(videoWidth);
videos.setVideoPath(upLoadPathDb);
videos.setCreateTime(new Date());
videos.setLikeCounts(0l);
videos.setCoverPath(coverPathDb+coverName);
videosService.save(videos);
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (fileOutputStream!=null){
fileOutputStream.flush();
fileOutputStream.close();
}
}
return LexJSONResult.ok(upLoadPathDb);
}
service层代码
/**
* 通过id查询bgm
* @param bgmId
* @return
*/
@Override
public Bgm findOne(String bgmId) {
return bgmDao.findOne(bgmId);
}
/**
* 保存视频到数据库
* @param videos
*/
@Override
@Transactional
public void save(Videos videos) {
videos.setId(IdUtils.getId());
videosDao.save(videos);
}
因为刚开始写上传接口时候为了方便,我将上面的一些字段都封装到了一个Videos对象中,接口中的入参为对象,之后就一直报415,所以当有文件上传时不要传入对象
用户在个人信息页面点击上传视频按钮后会调用微信小程序的api让用户在本机选择一段视频,要控制长度在30s以内且不能小于1s,选择完成后会跳转到下一个选择背景音乐的页面。
uploadVideo:function(){
var me =this;
//微信小程序提供的api,用于选择视频
wx.chooseVideo({
sourceType: ['album', 'camera'],
success(res) {
console.log(res)
//对视频的长度进行判断,如果小于1s则会提醒用户
if (duration<1){
wx.showToast({
title: '你的视频太短了',
icon:'none'
})
return;
}else{
//如果验证通过
//获得该视频的时间长度
var duration =res.duration;
//获得视频的高度
var height= res.height;
//获得视频的尺寸
var size =res.size;
//获得视频的临时路径
var tempFilePath =res.tempFilePath;
//获得视频的宽度
var width =res.width;
//跳转到选择背景音乐的界面,并将所获得的参数传递过去
wx.navigateTo({
url: '../chooseBgm/chooseBgm?duration=' + duration+
'&height=' + height+
'&size=' + size +
'&tempFilePath=' + tempFilePath +
'&width=' + width
})
}
}
})
}
当用户选择完视频后会跳转到该页面来,当该页面在加载的时候应该调用查询全部bgm的接口,并将数据显示在该页面上,用户可以选择自己喜欢的bgm,并且写入视频的相关描述,点击上传按钮,我们要获取到用户所选的bgmid以及所写入的视频内容,并将这些作为参数,去调用后台的上传视频的接口
const app = getApp()
Page({
data: {
bgmList:{},
serverUrl:app.serverUrl,
videoParams:{}
},
onLoad:function(params){
var me=this;
var serverUrl=app.serverUrl;
//获取到上个页面传入的参数,并保存在videoParams
me.setData({
videoParams:params
})
//调用查询全部bgm的接口
wx.request({
url: serverUrl + '/bgmList',
method:'POST',
header: {
'content-type': 'application/json'
},
success:function(res){
//将返回值赋值给bgmList,在页面上遍历出来
me.setData({
bgmList:res.data.data
})
console.log(res.data)
}
})
},
//上传方法
upload:function(e){
var me =this;
//获取视频的时长
var duration=me.data.videoParams.duration;
//获取视频的高度
var height= me.data.videoParams.height;
//获取上个页面返回的该视频的临时路径
var tempFilePath = me.data.videoParams.tempFilePath;
//获取该视频的宽度
var width =me.data.videoParams.width;
//获取用户所选择的bgmId
var bgmId= e.detail.value.bgmId;
//获取用户所输入的内容
var desc = e.detail.value.desc;
var serverUrl=app.serverUrl;
// var user=app.userInfo;
var user = app.getGlobalUserInfo("userInfo");
wx.showLoading({
title: '上传中',
})
//上传时候同样需要进行身份认证
wx.uploadFile({
url: serverUrl + '/video/uploadVideo',
filePath: tempFilePath,
name: 'file',
header: {
'content-type': 'application/json',
'userId': user.id,
'userToken': user.token
},
//在uploadFile中如果要传多个数据可以使用formData
formData:{
id:user.id,
videoSeconds: duration,
videoHeight: height,
videoWidth: width,
bgmId: bgmId,
desc: desc
},
success(res) {
console.log(res.data)
// const data = JSON.parse(res.data);
wx.hideLoading();
wx.showToast({
title: '上传成功',
})
wx.redirectTo({
url: '../mine/mine',
})
}
})
console.log(bgmId,desc)
}
})