package com.goafanti.admin.controller; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Calendar; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import net.bramp.ffmpeg.FFmpeg; import net.bramp.ffmpeg.FFmpegExecutor; import net.bramp.ffmpeg.FFmpegUtils; import net.bramp.ffmpeg.FFprobe; import net.bramp.ffmpeg.builder.FFmpegBuilder; import net.bramp.ffmpeg.job.FFmpegJob; import net.bramp.ffmpeg.probe.FFmpegProbeResult; import net.bramp.ffmpeg.progress.Progress; import net.bramp.ffmpeg.progress.ProgressListener; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.goafanti.admin.service.AdminVideoService; import com.goafanti.common.bo.Result; import com.goafanti.common.constant.ErrorConstants; import com.goafanti.common.controller.CertifyApiController; import com.goafanti.common.enums.AttachmentType; import com.goafanti.common.enums.VideoFields; import com.goafanti.common.model.JtVideo; import com.goafanti.common.utils.FtpUtils; import com.goafanti.common.utils.StringUtils; import com.goafanti.core.shiro.token.TokenManager; @Controller @RequestMapping(value = "/api/admin/video") public class AdminVideoApiController extends CertifyApiController { @Resource private AdminVideoService adminVideoService; @Value(value = "${ffmpeg.path}") public String ffmpegPath = null; @Value(value = "${ffprobe.path}") public String ffprobePath = null; // 上传视频只做MP4格式的 @RequestMapping(value = "/upload", method = RequestMethod.POST) @ResponseBody public Result uploadPicture(HttpServletRequest req, String sign) { Result res = new Result(); AttachmentType attachmentType = AttachmentType.getField(sign); if (attachmentType == AttachmentType.VIDEO) { // 先将视频存在本地文件中 String filename = handleVideoFiles(res, req); res.setData(filename); } else if (attachmentType == AttachmentType.VIDEO_COVER) { String picturebase = req.getParameter("picturebase"); String filename = req.getParameter("filename"); byte[] bs = Base64.getDecoder().decode(picturebase); filename = System.nanoTime() + filename.substring(filename.indexOf(".")); res.setData(handleBaseFiles(res, "/video_cover/", false, bs, sign, TokenManager.getUserId(), filename)); } else { res.getError().add( buildError(ErrorConstants.PARAM_ERROR, "", "文件标示")); } return res; } private String createFileName() { // 年月日 Calendar now = Calendar.getInstance(); int year = now.get(Calendar.YEAR); int month = now.get(Calendar.MONTH) + 1; int day = now.get(Calendar.DAY_OF_MONTH); String dir = "/" + year + "/" + month + "/" + day; return dir; } // 查看所有视频信息 @RequestMapping(value = "/getVideoList", method = RequestMethod.GET) @ResponseBody public Result getVideoList(JtVideo video, Integer pageNo, Integer pageSize) { Result result = new Result(); result.setData(adminVideoService.getVideoList(video, pageNo, pageSize,1)); return result; } // 查看单个信息 @RequestMapping(value = "/getVideoById", method = RequestMethod.GET) @ResponseBody public Result getVideoById(String id) { Result result = new Result(); result.setData(adminVideoService.getVideoById(id)); return result; } // 新增视频信息 @RequestMapping(value = "/insertVideo", method = RequestMethod.POST) @ResponseBody public Result insertVideo(@Valid JtVideo video , BindingResult bindingResult) throws IOException, InterruptedException, ExecutionException { // 数据限制 Result result = new Result(); if (bindingResult.hasErrors()) { result.getError().add(buildErrorByMsg(bindingResult.getFieldError().getDefaultMessage(), VideoFields.getFieldDesc(bindingResult.getFieldError().getField()))); return result; } String filename = video.getUrl(); // 生成保存到数据库的url String output = System.nanoTime() + ".mp4"; video.setUrl(createFileName() + "/" + output); video.setTranscoding(1); // 保存到数据库 result.setData(adminVideoService.insertVideo(video)); // 压缩视频并将视频 videoChange(filename, output, video.getUrl()); return result; } // 修改视频 @RequestMapping(value = "/updateVideo", method = RequestMethod.POST) @ResponseBody public Result updateVideo(@Valid JtVideo video , BindingResult bindingResult) throws IOException, InterruptedException, ExecutionException { Result result = new Result(); if (bindingResult.hasErrors()) { result.getError().add(buildErrorByMsg(bindingResult.getFieldError().getDefaultMessage(), VideoFields.getFieldDesc(bindingResult.getFieldError().getField()))); return result; } JtVideo oldVideo = adminVideoService.getVideoById(video.getId()); if (StringUtils.isBlank(video.getId())) { result.getError().add( buildError(ErrorConstants.PARAM_ERROR, "", "视频id不能为空")); return result; } else if (null == oldVideo) { result.getError().add( buildError(ErrorConstants.PARAM_ERROR, "", "视频id不存在")); return result; } if (StringUtils.isNotBlank(video.getUrl()) && !video.getUrl().equals(oldVideo.getUrl())) { // 如果修改了视频 video.setTranscoding(1); // 压缩视频并将视频 String filename = video.getUrl(); String output = System.nanoTime() + ".mp4"; video.setUrl(createFileName() + "/" + output); videoChange(filename, output, video.getUrl()); }else{ video.setUrl(null); } result.setData(adminVideoService.updateVideo(video)); return result; } // 将文件转码 private String videoChange(String input, String output, String url) throws IOException, InterruptedException, ExecutionException { FFmpeg ffmpeg = new FFmpeg(ffmpegPath); FFprobe ffprobe = new FFprobe(ffprobePath); FFmpegProbeResult in = ffprobe.probe(input); FFmpegBuilder builder = new FFmpegBuilder(); builder.setInput(in) // 输入文件 .overrideOutputFiles(true) // 覆盖重复文件 .addOutput(output) // 输出文件 .setFormat("mp4") // 设置格式 .setTargetSize(250_000) // 目标大小 .disableSubtitle() // 没有子标题 .setAudioChannels(1) // 声道 .setAudioSampleRate(48_000) // 音频采样率 .setAudioBitRate(32768) // 音频传输率 .setVideoCodec("libx264") // 视频编解码器 .setVideoFrameRate(24, 1) // 视频帧速率 .setVideoResolution(640, 480) // 视频分辨率 .setStrict(FFmpegBuilder.Strict.EXPERIMENTAL) // 严格形式 .done(); FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe); ExecutorService service = Executors.newCachedThreadPool(); // 3.直接new一个FutureTask SubTask subTask = new SubTask(executor, builder, in, url, input, output); FutureTask result = new FutureTask(subTask); // 4.提交任务 service.submit(result); // 5.关闭线程池 service.shutdown(); // LOGGER.info("=============返回给前端============="); return "SUCESS"; } private void updateSql(String url, String filename) { // 修改数据 File file = new File(filename); JtVideo video = new JtVideo(); video.setMd5(FileMD5(file)); video.setTranscoding(2); video.setUrl(url); int c = adminVideoService.updateByUrl(video); if (c > 0){ url = url.substring(0,url.lastIndexOf("/")); videoChange(url, filename);// 上传到远程 } } private void videoChange(String url, String filename) { File f2 = new File(filename);// 临时压缩后的文件 if (f2.exists()) { // 传到远程服务器 FtpUtils ftp = new FtpUtils(); ftp.uploadFile(url, filename, filename); // 删除视频 f2.delete(); } } // 获得文件md5 private String FileMD5(File file) { try { FileInputStream fis = new FileInputStream(file); MessageDigest md = MessageDigest.getInstance("MD5"); byte[] buffer = new byte[1024]; int length = -1; while ((length = fis.read(buffer, 0, 1024)) != -1) { md.update(buffer, 0, length); } BigInteger bigInt = new BigInteger(1, md.digest()); return bigInt.toString(16); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } class SubTask implements Callable { private Boolean state = false; private FFmpegExecutor executor; private FFmpegBuilder builder; private FFmpegProbeResult probeResult; private String url; private String input; private String output; public SubTask(FFmpegExecutor executor, FFmpegBuilder builder, FFmpegProbeResult probeResult, String url, String input, String output) { this.executor = executor; this.builder = builder; this.probeResult = probeResult; this.url = url; this.input = input; this.output = output; } public FFmpegExecutor getExecutor() { return executor; } public FFmpegBuilder getBuilder() { return builder; } public String getUrl() { return url; } public String getInput() { return input; } public String getOutput() { return output; } public Boolean getState() { return state; } @Override public Boolean call() throws Exception { final double duration_ns = probeResult.getFormat().duration * TimeUnit.SECONDS.toNanos(1); try { FFmpegJob job = executor.createJob(builder, new ProgressListener() { @Override public void progress(Progress progress) { double percentage = progress.out_time_ns / duration_ns; System.out.println(String .format("[%.0f%%] status:%s frame:%d time:%s ms fps:%.0f speed:%.2fx", percentage * 100, progress.status, progress.frame, FFmpegUtils.toTimecode( progress.out_time_ns, TimeUnit.NANOSECONDS), progress.fps.doubleValue(), progress.speed)); } }); job.run(); // 6.阻塞 call // System.out.println("设置 sate"); // latch.wait(); state = true; updateSql(url, output); // 删除未压缩的视频 File f1 = new File(input); f1.delete(); } catch (Exception e) { e.printStackTrace(); } return state; } } }