将视频文件(mp4,mov…)转化为m3u8文件
工具类
package cn.creatoo.file.utils;
import cn.creatoo.common.minio.config.MinioConfig;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author shang tf
* @version 1.0
* @data 2023/12/22 9:57
*/
@Component
public class FileCoverUtils{
@Autowired
private AmazonS3 amazonS3;
@Autowired
private MinioConfig minioConfig;
//执行成功0,失败1
private int CODE_SUCCESS = 0;
private int CODE_FAIL = 1;
// 下载到本地的地址("D:\\local\\m3u8"或"/opt/m3u8temp")
@Value("${localM3u8Path}")
private String localDirPath;
public String uploadM3U8(String remoteVideoUrl) {
String fileDate = "";
String fileName = "";
String fileNameNotSuffix = "";
try {
Pattern pattern = Pattern.compile("\\d{4}/\\d{2}/\\d{2}");
Matcher matcher = pattern.matcher(remoteVideoUrl);
if (matcher.find()) {
fileDate = matcher.group();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
URL fileUrl = new URL(remoteVideoUrl);
fileName = fileUrl.getFile();
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
String filePath = fileUrl.getPath();
fileNameNotSuffix = filePath.substring(filePath.lastIndexOf("/") + 1);
fileNameNotSuffix = fileName.substring(0, fileName.lastIndexOf("."));
} catch (Exception e) {
e.printStackTrace();
}
String localVideoPath = localDirPath + File.separator + fileNameNotSuffix + File.separator + fileName;
// 将mp4文件转为ts文件
String cmd_mp4_2_ts = " -y -i " + localDirPath + File.separator+ fileNameNotSuffix + File.separator + fileName + " -vcodec copy -acodec copy -vbsf h264_mp4toannexb " + localDirPath + File.separator + fileNameNotSuffix +File.separator + fileNameNotSuffix + "_ts.ts ";
// 将ts文件分割
String cmd_ts_split = " -i " + localDirPath +File.separator+ fileNameNotSuffix + File.separator + fileNameNotSuffix + "_ts.ts" + " -c copy -map 0 -f segment -segment_list " + localDirPath +File.separator+ fileNameNotSuffix + File.separator + fileNameNotSuffix + "_m3u8.m3u8" + " -segment_time 15 " + localDirPath +File.separator+ fileNameNotSuffix + File.separator + fileNameNotSuffix + "_15s_%3d.ts ";
CompletableFuture<Integer> completableFutureTask = CompletableFuture.supplyAsync(() -> {
try {
downloadVideo(remoteVideoUrl, localVideoPath);
cmdExecut(cmd_mp4_2_ts);
return CODE_SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return CODE_FAIL;
}
}, ThreadPoolExecutorUtils.pool).thenApplyAsync((Integer code) -> {
if (CODE_SUCCESS != code) {
return CODE_FAIL;
}
System.out.println("第一步:视频整体转码ts,成功!");
Integer codeTmp = cmdExecut(cmd_ts_split);
if (CODE_SUCCESS != codeTmp) {
return CODE_FAIL;
}
System.out.println("第二步:ts 文件切片,成功!");
return codeTmp;
}, ThreadPoolExecutorUtils.pool);
try {
System.out.println(String.format("获取最终执行结果:%s", completableFutureTask.get() == CODE_SUCCESS ? "成功!" : "失败!"));
// 切片成功后上传远程仓库
String fileRemote = fileDate + "/" + fileNameNotSuffix + "/";
return uploadM3u8ToRemoteStorage(fileRemote,localDirPath + File.separator + fileNameNotSuffix);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
public void downloadVideo(String remoteUrl, String localPath) throws IOException {
URL url = new URL(remoteUrl);
// 如果此文件夹不存在则创建文件夹
File file = new File(localPath);
String directoryPath = file.getParent();
File directory = new File(directoryPath);
if (!directory.exists()) {
directory.mkdirs();
}
// 将远程链接的文件下载到本地
try (InputStream in = url.openStream()) {
Files.copy(in, Paths.get(localPath));
}
}
/**
* @throws
* @Description: (执行ffmpeg自定义命令)
* @param: @param cmdStr
* @param: @return
* @return: Integer
*/
public Integer cmdExecut(String cmdStr) {
//code=0表示正常
Integer code = null;
FfmpegCmd ffmpegCmd = new FfmpegCmd();
/**
* 错误流
*/
InputStream errorStream = null;
try {
//destroyOnRuntimeShutdown表示是否立即关闭Runtime
//如果ffmpeg命令需要长时间执行,destroyOnRuntimeShutdown = false
//openIOStreams表示是不是需要打开输入输出流:
// inputStream = processWrapper.getInputStream();
// outputStream = processWrapper.getOutputStream();
// errorStream = processWrapper.getErrorStream();
ffmpegCmd.execute(false, true, cmdStr);
errorStream = ffmpegCmd.getErrorStream();
//打印过程
int len = 0;
while ((len = errorStream.read()) != -1) {
System.out.print((char) len);
}
//code=0表示正常
code = ffmpegCmd.getProcessExitCode();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
ffmpegCmd.close();
}
//返回
return code;
}
public String uploadM3u8ToRemoteStorage(String fileRemote,String fileDir) {
// 上传TS文件到S3
File tsFilesDir = new File(fileDir);
try {
File[] tsFiles = tsFilesDir.listFiles();
String m3u8FileLink = "";
for (File tsFile : tsFiles) {
if (tsFile.getName().contains(".ts") || tsFile.getName().contains(".m3u8")) {
String remoteFilePath = fileRemote + tsFile.getName();
PutObjectRequest request = new PutObjectRequest(minioConfig.getBucketName(), remoteFilePath, tsFile);
amazonS3.putObject(request);
if (tsFile.getName().contains(".m3u8")) {
m3u8FileLink = minioConfig.getViewUrl() + "/" + minioConfig.getBucketName() + "/" + remoteFilePath;
}
}
}
System.out.println("文件上传成功!");
return m3u8FileLink;
} finally {
deleteDirectory(tsFilesDir);
}
}
// 删除生成的文件及目录
private void deleteDirectory(File directory) {
if (directory.exists()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
file.delete();
}
}
}
directory.delete();
}
}
}
上述方法使用到的类:
package cn.creatoo.file.utils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author shang tf
* @version 1.0
* @data 2023/12/11 16:58
*/
public class ThreadPoolExecutorUtils {
//多线程
public static int core = Runtime.getRuntime().availableProcessors();
public static ExecutorService pool = new ThreadPoolExecutor(core,//核心
core * 2,//最大
0L,//空闲立即退出
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024),//无边界阻塞队列
new ThreadPoolExecutor.AbortPolicy());
}
package cn.creatoo.file.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ws.schild.jave.process.ProcessKiller;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;
/**
*
* @Description:(cmd方式调用ffmpeg)
* @author: stf
* @date: 2021年6月22日 下午5:31:38
* @Copyright:
*/
public class FfmpegCmd {
private static final Logger LOG = LoggerFactory.getLogger(ProcessWrapper.class);
/** The process representing the ffmpeg execution. */
private Process ffmpeg = null;
/**
* A process killer to kill the ffmpeg process with a shutdown hook, useful if the jvm execution
* is shutted down during an ongoing encoding process.
*/
private ProcessKiller ffmpegKiller = null;
/** A stream reading from the ffmpeg process standard output channel. */
private InputStream inputStream = null;
/** A stream writing in the ffmpeg process standard input channel. */
private OutputStream outputStream = null;
/** A stream reading from the ffmpeg process standard error channel. */
private InputStream errorStream = null;
/**
* Executes the ffmpeg process with the previous given arguments.
*
* @param destroyOnRuntimeShutdown destroy process if the runtime VM is shutdown
* @param openIOStreams Open IO streams for input/output and errorout, should be false when
* destroyOnRuntimeShutdown is false too
* @param ffmpegCmd windows such as (mp4 transform to mov):
* " -i C:\\Users\\hsj\\AppData\\Local\\Temp\\jave\\honer.mp4 -c copy C:\\Users\\hsj\\AppData\\Local\\Temp\\jave\\honer_test.mov "
* @throws IOException If the process call fails.
*/
public void execute(boolean destroyOnRuntimeShutdown, boolean openIOStreams, String ffmpegCmd) throws IOException {
DefaultFFMPEGLocator defaultFFMPEGLocator = new DefaultFFMPEGLocator();
StringBuffer cmd = new StringBuffer(defaultFFMPEGLocator.getExecutablePath());
//insert blank for delimiter
cmd.append(" ");
cmd.append(ffmpegCmd);
String cmdStr = String.format("ffmpegCmd final is :%s", cmd.toString());
System.out.println(cmdStr);
LOG.info(cmdStr);
Runtime runtime = Runtime.getRuntime();
try {
ffmpeg = runtime.exec(cmd.toString());
if (destroyOnRuntimeShutdown) {
ffmpegKiller = new ProcessKiller(ffmpeg);
runtime.addShutdownHook(ffmpegKiller);
}
if (openIOStreams) {
inputStream = ffmpeg.getInputStream();
outputStream = ffmpeg.getOutputStream();
errorStream = ffmpeg.getErrorStream();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns a stream reading from the ffmpeg process standard output channel.
*
* @return A stream reading from the ffmpeg process standard output channel.
*/
public InputStream getInputStream() {
return inputStream;
}
/**
* Returns a stream writing in the ffmpeg process standard input channel.
*
* @return A stream writing in the ffmpeg process standard input channel.
*/
public OutputStream getOutputStream() {
return outputStream;
}
/**
* Returns a stream reading from the ffmpeg process standard error channel.
*
* @return A stream reading from the ffmpeg process standard error channel.
*/
public InputStream getErrorStream() {
return errorStream;
}
/** If there's a ffmpeg execution in progress, it kills it. */
public void destroy() {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable t) {
LOG.warn("Error closing input stream", t);
}
inputStream = null;
}
if (outputStream != null) {
try {
outputStream.close();
} catch (Throwable t) {
LOG.warn("Error closing output stream", t);
}
outputStream = null;
}
if (errorStream != null) {
try {
errorStream.close();
} catch (Throwable t) {
LOG.warn("Error closing error stream", t);
}
errorStream = null;
}
if (ffmpeg != null) {
ffmpeg.destroy();
ffmpeg = null;
}
if (ffmpegKiller != null) {
Runtime runtime = Runtime.getRuntime();
runtime.removeShutdownHook(ffmpegKiller);
ffmpegKiller = null;
}
}
/**
* Return the exit code of the ffmpeg process If the process is not yet terminated, it waits for
* the termination of the process
*
* @return process exit code
*/
public int getProcessExitCode() {
// Make sure it's terminated
try {
ffmpeg.waitFor();
} catch (InterruptedException ex) {
LOG.warn("Interrupted during waiting on process, forced shutdown?", ex);
}
return ffmpeg.exitValue();
}
/**close**/
public void close() {
destroy();
}
}
使用时:
@PostMapping("/uploadM3U8")
@Async
public R<String> uploadM3U8(@RequestBody String remoteVideoUrl){
String uploadM3U8 = fileCoverUtils.uploadM3U8(remoteVideoUrl);
return R.ok(uploadM3U8);
}
其他模块使用openfeign远程调用
这里使用了
CompletableFuture<R<String>>
来做接收,返回的m3u8链接我们可以通过原文件地址推测出来,可以异步执行,不必等待接口返回结果。因为上传视频可能要很久,如果同步执行会导致openfeign调用失败(openfeign默认1秒内要返回结果,否则就会报错)
package cn.creatoo.system.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import cn.creatoo.common.core.constant.ServiceNameConstants;
import cn.creatoo.common.core.domain.R;
import cn.creatoo.system.api.factory.RemoteFileFallbackFactory;
import java.util.concurrent.CompletableFuture;
/**
* 文件服务
*
* @author ldq
*/
@FeignClient(contextId = "remoteFileService", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class)
public interface RemoteFileService
{
/**
* 将视频链接上传M3U8格式
* @param remoteVideoUrl
* @return
*/
@PostMapping("uploadM3U8")
CompletableFuture<R<String>> uploadM3U8(@RequestBody String remoteVideoUrl);
}
降级处理类
package cn.creatoo.system.api.factory;
import cn.creatoo.common.core.domain.vo.WaterVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import cn.creatoo.common.core.domain.R;
import cn.creatoo.system.api.RemoteFileService;
import cn.creatoo.system.api.domain.SysFile;
import java.util.concurrent.CompletableFuture;
/**
* 文件服务降级处理
*
* @author ldq
*/
@Component
public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileService>
{
private static final Logger log = LoggerFactory.getLogger(RemoteFileFallbackFactory.class);
@Override
public RemoteFileService create(Throwable throwable)
{
log.error("文件服务调用失败:{}", throwable.getMessage());
return new RemoteFileService()
{
@Override
public CompletableFuture<R<String>> uploadM3U8(String remoteVideoUrl) {
return CompletableFuture.supplyAsync(()-> R.fail("上传m3u8文件失败:"+throwable.getMessage()));
}
};
}
}
附:
文件工具类
package cn.creatoo.file.utils;
import cn.creatoo.common.core.domain.vo.FileUploadCenterParam;
import cn.creatoo.common.core.domain.vo.file.CompressCoverLink;
import cn.creatoo.common.minio.config.CenterMinioConfig;
import cn.creatoo.common.minio.config.MinioConfig;
import cn.creatoo.file.mapper.SysUploadTaskMapper;
import cn.hutool.core.util.ObjectUtil;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import lombok.extern.log4j.Log4j2;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
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.info.VideoInfo;
import ws.schild.jave.info.VideoSize;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author shang tf
* @version 1.0
* @data 2023/9/22 11:10
* 文件掩饰工具类,对原文件进行水印,裁剪等
*/
@Component
@Log4j2
public class FileCoverUtils {
@Autowired
private MinioConfig minioConfig;
// 本系统的上传AmazonS3
@Autowired
@Qualifier("amazonS3Client")
private AmazonS3 amazonS3;
// 生产端的上传AmazonS3
@Autowired
private AmazonS3 centerAmazonS3Client;
@Autowired
private CenterMinioConfig centerMinioConfig;
// 上传到生产端时,临时下载到本地地址
@Value("${localFilePath}")
private String localFilePath;
@Value("${resource.waterMarkImage}")
private String waterMarkImage;
@Value("${minio.bucketName}")
private String bucketName;
private SysUploadTaskMapper sysUploadTaskMapper;
/**
* 水印视频名字拼接
*/
final String NEW_VIDOE_WATER_NAME_PRE_STR = "_water";
//gif输出格式
private final String outputFormat = "gif";
//执行成功0,失败1
private int CODE_SUCCESS = 0;
private int CODE_FAIL = 1;
@Value("${localM3u8Path}")
private String localDirPath;
public String uploadM3U8(String remoteVideoUrl) {
String fileDate = "";
String fileName = "";
String fileNameNotSuffix = "";
try {
Pattern pattern = Pattern.compile("\\d{4}/\\d{2}/\\d{2}");
Matcher matcher = pattern.matcher(remoteVideoUrl);
if (matcher.find()) {
fileDate = matcher.group();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
URL fileUrl = new URL(remoteVideoUrl);
fileName = fileUrl.getFile();
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
String filePath = fileUrl.getPath();
fileNameNotSuffix = filePath.substring(filePath.lastIndexOf("/") + 1);
fileNameNotSuffix = fileName.substring(0, fileName.lastIndexOf("."));
} catch (Exception e) {
e.printStackTrace();
}
String localVideoPath = localDirPath + File.separator + fileNameNotSuffix + File.separator + fileName;
// 将ts文件分割
String cmd_ts_split = " -i " + localDirPath + File.separator + fileNameNotSuffix + File.separator + fileNameNotSuffix + "_ts.ts" + " -c copy -map 0 -f segment -segment_list " + localDirPath + File.separator + fileNameNotSuffix + File.separator + fileNameNotSuffix + "_m3u8.m3u8" + " -segment_time 15 " + localDirPath + File.separator + fileNameNotSuffix + File.separator + fileNameNotSuffix + "_15s_%3d.ts ";
String finalFileNameNotSuffix = fileNameNotSuffix;
CompletableFuture<Integer> completableFutureTask = CompletableFuture.supplyAsync(() -> {
try {
String encodePath = downloadVideo(remoteVideoUrl, localVideoPath);
// 将mp4文件转为ts文件
String cmd_mp4_2_ts = " -y -i " + encodePath + " -vcodec copy -acodec copy -vbsf h264_mp4toannexb " + localDirPath + File.separator + finalFileNameNotSuffix + File.separator + finalFileNameNotSuffix + "_ts.ts ";
cmdExecut(cmd_mp4_2_ts);
return CODE_SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return CODE_FAIL;
}
}, ThreadPoolExecutorUtils.pool).thenApplyAsync((Integer code) -> {
if (CODE_SUCCESS != code) {
return CODE_FAIL;
}
System.out.println("第一步:视频整体转码ts,成功!");
Integer codeTmp = cmdExecut(cmd_ts_split);
if (CODE_SUCCESS != codeTmp) {
return CODE_FAIL;
}
System.out.println("第二步:ts 文件切片,成功!");
return codeTmp;
}, ThreadPoolExecutorUtils.pool);
try {
System.out.println(String.format("获取最终执行结果:%s", completableFutureTask.get() == CODE_SUCCESS ? "成功!" : "失败!"));
// 切片成功后上传远程仓库
String fileRemote = fileDate + "/" + fileNameNotSuffix + "/";
return uploadM3u8ToRemoteStorage(fileRemote, localDirPath + File.separator + fileNameNotSuffix);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
public String downloadVideo(String remoteUrl, String localPath) throws IOException {
URL url = new URL(remoteUrl);
// 如果此文件夹不存在则创建文件夹
File source = new File(localPath);
String directoryPath = source.getParent();
File directory = new File(directoryPath);
if (!directory.exists()) {
directory.mkdirs();
}
// 将远程链接的文件下载到本地
try (InputStream in = url.openStream()) {
// 将文件下载到本地
Files.copy(in, Paths.get(localPath));
// 将视频文件进行转码!!
long start = System.currentTimeMillis();
String fileName = source.getName();
int lastDotIndex = fileName.lastIndexOf(".");
String nameWithoutExtension = fileName.substring(0, lastDotIndex);
String extension = fileName.substring(lastDotIndex);
String newFileName = nameWithoutExtension + "_encode" + extension;
String newFilePath = source.getParent() + File.separator + newFileName;
File target = new File(newFilePath);
AudioAttributes audio = new AudioAttributes();
audio.setCodec("aac");
//audio.setBitRate(236000 / 2);
audio.setChannels(2);
audio.setSamplingRate(8000);
VideoAttributes video = new VideoAttributes();
video.setCodec("h264");
//video.setBitRate(1000000);
video.setFrameRate(25);
//video.setQuality(10);
//video.setSize(new VideoSize(720, 480));
EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat("mp4");
attrs.setAudioAttributes(audio);
attrs.setVideoAttributes(video);
Encoder encoder = new Encoder();
try {
encoder.encode(new MultimediaObject(source), target, attrs);
return newFilePath;
} catch (Exception e) {
e.printStackTrace();
System.out.println(encoder.getUnhandledMessages());
} finally {
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start) + "ms");
}
return null;
}
}
/**
* @throws
* @Description: (执行ffmpeg自定义命令)
* @param: @param cmdStr
* @param: @return
* @return: Integer
*/
public Integer cmdExecut(String cmdStr) {
//code=0表示正常
Integer code = null;
FfmpegCmd ffmpegCmd = new FfmpegCmd();
/**
* 错误流
*/
InputStream errorStream = null;
try {
//destroyOnRuntimeShutdown表示是否立即关闭Runtime
//如果ffmpeg命令需要长时间执行,destroyOnRuntimeShutdown = false
//openIOStreams表示是不是需要打开输入输出流:
// inputStream = processWrapper.getInputStream();
// outputStream = processWrapper.getOutputStream();
// errorStream = processWrapper.getErrorStream();
ffmpegCmd.execute(false, true, cmdStr);
errorStream = ffmpegCmd.getErrorStream();
//打印过程
int len = 0;
while ((len = errorStream.read()) != -1) {
System.out.print((char) len);
}
//code=0表示正常
code = ffmpegCmd.getProcessExitCode();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
ffmpegCmd.close();
}
//返回
return code;
}
public String uploadM3u8ToRemoteStorage(String fileRemote, String fileDir) {
// 上传TS文件到S3
File tsFilesDir = new File(fileDir);
try {
File[] tsFiles = tsFilesDir.listFiles();
String m3u8FileLink = "";
if (ObjectUtil.isNotEmpty(tsFiles)) {
for (File tsFile : tsFiles) {
// ts,m3u8文件上传服务器
if (tsFile.getName().contains(".ts") || tsFile.getName().contains(".m3u8")) {
// 把完整的ts文件去除,不上传
if (tsFile.getName().contains("_ts.ts")) {
continue;
}
String remoteFilePath = fileRemote + tsFile.getName();
PutObjectRequest request = new PutObjectRequest(minioConfig.getBucketName(), remoteFilePath, tsFile);
amazonS3.putObject(request);
if (tsFile.getName().contains(".m3u8")) {
m3u8FileLink = minioConfig.getViewUrl() + "/" + minioConfig.getBucketName() + "/" + remoteFilePath;
}
}
}
log.info("文件上传成功!");
return m3u8FileLink;
}
log.warn("文件上传失败!");
return null;
} finally {
deleteDirectory(tsFilesDir);
}
}
private void deleteDirectory(File directory) {
if (directory.exists()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
file.delete();
}
}
}
directory.delete();
}
}
/**
* 提供音频链接,将音频裁切,返回裁切后音频的url
*
* @param src
* @return
* @throws EncoderException
* @throws IOException
*/
public String cutAudio(String src) throws Exception {
File srcFile = null;
File targetFile = null;
try {
URL url = new URL(src);
URLConnection connection = url.openConnection();
InputStream inputStream = connection.getInputStream();
// 从src的url中获取到要切的文件名、日期目录
URL fileUrl = new URL(src);
File file = new File(fileUrl.getPath());
String fileName = file.getName();
int dotIndex = fileName.lastIndexOf(".");
String suffix = fileName.substring(dotIndex);
String name = "/" + bucketName + "/";
int startIndex = src.indexOf(name) + name.length();
int endIndex = startIndex + 10; // 日期字符串长度为8
String date = src.substring(startIndex, endIndex);
String year = date.substring(0, 4);
String month = date.substring(5, 7);
String day = date.substring(8, 10);
// 临时文件
srcFile = new File(fileName.substring(0, dotIndex - 1) + "temp" + suffix);
FileOutputStream outputStream = new FileOutputStream(srcFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
//将文件名md5加密
MessageDigest md = MessageDigest.getInstance("MD5");
// 将输入字符串转换为字节数组
byte[] inputBytes = fileName.getBytes();
// 计算MD5摘要
byte[] hash = md.digest(inputBytes);
// 将字节数组转换为十六进制字符串
//加密后的文件名
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
// 裁切的文件名拼接上_cutAudio。
String target = "cutAudio_" + hexString + ".mp3";
// 裁剪的文件
targetFile = new File(target);
Encoder encoder = new Encoder();
MultimediaObject srcMultiObj = new MultimediaObject(srcFile);
MultimediaInfo srcMediaInfo = srcMultiObj.getInfo();
// 拿到原音频的时长(毫秒)
long duration = srcMediaInfo.getDuration();
long mediaSecond = 0;
if (duration > 0) {
// 转换为秒
mediaSecond = duration / 1000;
}
EncodingAttributes encodingAttributes = new EncodingAttributes();
// 大于60秒小于3分钟 剪切60秒;大于3分钟剪切1分钟
if (mediaSecond > 60) {
// 设置起始偏移量(秒)
encodingAttributes.setOffset(0.0F);
// 设置切片的音频长度(秒)
encodingAttributes.setDuration(30.0F);
} else if (duration <= 60) {
// 设置起始偏移量(秒)
encodingAttributes.setOffset(0.0F);
// 设置切片的音频长度(秒)
encodingAttributes.setDuration(Float.valueOf(duration / 2));
}
// 设置音频属性
AudioAttributes audio = new AudioAttributes();
audio.setBitRate(srcMediaInfo.getAudio().getBitRate());
audio.setSamplingRate(srcMediaInfo.getAudio().getSamplingRate());
audio.setChannels(srcMediaInfo.getAudio().getChannels());
String codec = srcMediaInfo.getAudio().getDecoder().split(" ")[0];
if (codec.equals("aac")) {
codec = "mp3";
}
audio.setCodec(codec);
encodingAttributes.setOutputFormat("mp3");
encodingAttributes.setAudioAttributes(audio);
// 写文件
encoder.encode(srcMultiObj, targetFile, encodingAttributes);
// 将裁剪后的文件保存到 Amazon S3
amazonS3.putObject(minioConfig.getBucketName(), year + "/" + month + "/" + day + "/" + target, targetFile);
String[] split = src.split("/", 5);
String s = split[2];
String replace = src.replace(fileName, target);
System.out.println("replace = " + replace);
return replace;
} finally {
// 在 finally 块中删除临时文件
deleteTempFile(srcFile);
deleteTempFile(targetFile);
}
}
/**
* 删除临时文件
*
* @param file
*/
private void deleteTempFile(File file) {
if (file != null && file.exists()) {
file.delete();
}
}
/**
* pdf 添加文字水印
*
* @param sourceUrl pdf远程地址
* @param watermarkText 添加的文字水印
* @return
* @throws IOException
* @throws DocumentException
*/
public String addPdfWatermarkWithWord(String sourceUrl, String watermarkText)
throws IOException, DocumentException {
URL url = new URL(sourceUrl);
InputStream inputStream = url.openStream();
PdfReader reader = new PdfReader(inputStream);
int pageCount = reader.getNumberOfPages();
// 获取源文件的文件名和路径
String sourceFileName = Paths.get(url.getPath()).getFileName().toString();
String sourceDirectory = Paths.get(url.getPath()).getParent().toString();
// 指定生成的带水印的PDF文件名和路径
String outputFileName = "watermarked_" + sourceFileName;
String outputFilePath = Paths.get(sourceDirectory, outputFileName).toString();
// 创建输出文件的目录(如果不存在)
Path outputDirectory = Paths.get(sourceDirectory);
if (!Files.exists(outputDirectory)) {
Files.createDirectories(outputDirectory);
}
// 创建输出流,并使用生成的带水印的PDF文件路径
OutputStream outputStream = new FileOutputStream(outputFilePath);
PdfStamper stamper = new PdfStamper(reader, outputStream);
Font font = new Font(Font.FontFamily.HELVETICA, 48, Font.BOLD, BaseColor.RED);
for (int i = 1; i <= pageCount; i++) {
Phrase watermark = new Phrase(watermarkText, font);
PdfContentByte content = stamper.getUnderContent(i);
ColumnText.showTextAligned(content, Element.ALIGN_CENTER, watermark, 297, 420, 45);
}
stamper.close();
reader.close();
inputStream.close();
outputStream.close();
String date = sourceUrl.substring(sourceUrl.lastIndexOf("_") + 1, sourceUrl.lastIndexOf("_") + 9);
String year = date.substring(0, 4);
String month = date.substring(4, 6);
String day = date.substring(6, 8);
// 上传生成的带水印的PDF文件到Amazon S3
uploadToS3(outputFilePath, year + "/" + month + "/" + day + "/" + outputFileName);
String replace = sourceUrl.replace(sourceFileName, outputFileName);
return replace;
}
/**
* 给pdf添加图像水印,返回添加水印后的url地址
*
* @param sourceUrl 要添加水印的PDF地址
* @return 添加水印的url地址
* @throws IOException
* @throws DocumentException
*/
public String addPdfWatermarkWithImage(String sourceUrl)
throws Exception {
URL url = new URL(sourceUrl);
// 添加水印的地址
InputStream inputStream = url.openStream();
PdfReader reader = new PdfReader(inputStream);
int pageCount = reader.getNumberOfPages();
// 获取源文件的文件名和路径
String sourceFileName = Paths.get(url.getPath()).getFileName().toString();
String sourceDirectory = Paths.get(url.getPath()).getParent().toString();
//将文件名md5加密
MessageDigest md = MessageDigest.getInstance("MD5");
// 将输入字符串转换为字节数组
byte[] inputBytes = sourceFileName.getBytes();
// 计算MD5摘要
byte[] hash = md.digest(inputBytes);
// 将字节数组转换为十六进制字符串
//加密后的文件名
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
String outputFileName = "watermark_" + hexString + ".pdf";
// 指定生成的带水印的PDF文件名和路径
String outputFilePath = Paths.get(sourceDirectory, outputFileName).toString();
// 创建输出文件的目录(如果不存在)
Path outputDirectory = Paths.get(sourceDirectory);
if (!Files.exists(outputDirectory)) {
Files.createDirectories(outputDirectory);
}
// 创建输出流,并使用生成的带水印的PDF文件路径
OutputStream outputStream = new FileOutputStream(outputFilePath);
PdfStamper stamper = new PdfStamper(reader, outputStream);
for (int i = 1; i <= pageCount; i++) {
PdfContentByte content = stamper.getOverContent(i);
Image watermarkImage = Image.getInstance(waterMarkImage);
// 设置水印图片的位置,这里示例为在顶部居中
watermarkImage.setAbsolutePosition((reader.getPageSize(i).getWidth() - watermarkImage.getWidth()) / 2, reader.getPageSize(i).getHeight() / 2);
content.addImage(watermarkImage);
}
stamper.close();
reader.close();
inputStream.close();
outputStream.close();
String[] split = sourceUrl.split("/");
String day = split[split.length - 2];
String month = split[split.length - 3];
String year = split[split.length - 4];
// 上传生成的带水印的PDF文件到Amazon S3
uploadToS3(outputFilePath, year + "/" + month + "/" + day + "/" + outputFileName);
String[] split1 = sourceUrl.split("/", 5);
String s = split1[2];
String replace = sourceUrl.replace(sourceFileName, outputFileName);
System.out.println("replace = " + replace);
return replace;
}
/**
* 上传远程存储
*
* @param filePath 文件地址
* @param sourceFileName 上传文件名称(这里格式为:年/月/日/文件名.后缀)
*/
private void uploadToS3(String filePath, String sourceFileName) {
// 设置要上传的S3存储桶名称和对象键
String bucketName = minioConfig.getBucketName();
// 执行文件上传
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, sourceFileName, new File(filePath));
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("application/pdf");
putObjectRequest.setMetadata(metadata);
PutObjectResult putObjectResult = amazonS3.putObject(putObjectRequest);
}
/**
* 图片加水印
*
* @param fileLink
* @param relativePath
* @return
* @throws IOException
*/
public String method(String fileLink, String relativePath) throws Exception {
// 读取原图片信息 得到文件
URL url = new URL(fileLink);
URLConnection urlConnection = url.openConnection();
InputStream inputStream1 = urlConnection.getInputStream();
inputStream1.close();
//将文件对象转化为图片对象
java.awt.Image srcImg = ImageIO.read(url.openStream());
//获取图片的宽
int srcImgWidth = srcImg.getWidth(null);
//获取图片的高
int srcImgHeight = srcImg.getHeight(null);
System.out.println("图片的宽:" + srcImgWidth);
System.out.println("图片的高:" + srcImgHeight);
BufferedImage bufImg = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage.TYPE_INT_RGB);
// 加水印
//创建画笔
Graphics2D g = bufImg.createGraphics();
//绘制原始图片
g.drawImage(srcImg, 0, 0, srcImgWidth, srcImgHeight, null);
// 水印文件
URL relativeUrl = new URL(relativePath);
java.awt.Image relativeImg = ImageIO.read(relativeUrl.openStream());
//获取水印图片的宽度
int widthWaterMark = relativeImg.getWidth(null);
//获取水印图片的高度
int heightWaterMark = relativeImg.getHeight(null);
//设置 alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.9f));
//绘制水印图片 坐标为中间位置
g.drawImage(relativeImg, (srcImgWidth - widthWaterMark) / 2,
(srcImgHeight - heightWaterMark) / 2, widthWaterMark, heightWaterMark, null);
// 水印文件结束
g.dispose();
// 输出图片
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(bufImg, "png", outputStream);
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
//判断是否是阿里云上传
String[] split = fileLink.split("/", 5);
//判断是否符合
String dName = split[2];
String year;
String month;
String day;
String name = "/" + bucketName + "/";
int startIndex = fileLink.indexOf(name) + name.length();
int endIndex = startIndex + 10; // 日期字符串长度为8
String date = fileLink.substring(startIndex, endIndex);
year = date.substring(0, 4);
month = date.substring(5, 7);
day = date.substring(8, 10);
//获取文件名
String path = url.getPath();
String fileName = Paths.get(path).getFileName().toString();
//将文件名md5加密
MessageDigest md = MessageDigest.getInstance("MD5");
// 将输入字符串转换为字节数组
byte[] inputBytes = fileName.getBytes();
// 计算MD5摘要
byte[] hash = md.digest(inputBytes);
// 将字节数组转换为十六进制字符串
//加密后的文件名
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
String waterFileName = "water_" + hexString + ".jpg";
//将图片上传到minio服务器中
//设置图片的MIME类型
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("image/jpeg");
amazonS3.putObject(minioConfig.getBucketName(), year + "/" + month + "/" + day + "/" + waterFileName, inputStream, metadata);
String replace = null;
if (ObjectUtils.isNotEmpty(dName)) {
replace = fileLink.replace(fileName, waterFileName);
}
System.out.println("replace = " + replace);
System.out.println("图片添加水印完成");
outputStream.flush();
outputStream.close();
return replace;
}
/**
* 视频加水印
*
* @param inputFilePath
* @param outputFilePath
* @param watermarkText
* @return
* @throws Exception
*/
public String addWatermarkToVideo(String inputFilePath, String outputFilePath, String watermarkText) throws Exception {
//获取文件名
URL url = new URL(inputFilePath);
// 临时视频文件
URLConnection connection = url.openConnection();
InputStream inputStream = connection.getInputStream();
// 从src的url中获取到要切的文件名、日期目录
URL fileUrl = new URL(inputFilePath);
File file = new File(fileUrl.getPath());
String fileName = file.getName();
String path = file.getPath();
//获取日期
String name = "/" + bucketName + "/";
int startIndex = inputFilePath.indexOf(name) + name.length();
int endIndex = startIndex + 10; // 日期字符串长度为8
String date = inputFilePath.substring(startIndex, endIndex);
String year = date.substring(0, 4);
String month = date.substring(5, 7);
String day = date.substring(8, 10);
// 临时文件
File srcFile = new File(fileName);
FileOutputStream outputStream = new FileOutputStream(srcFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
//获取下载的路径
String fileLinkUrl = srcFile.getPath();//下载后的本地文件地址
//临时水印文件
URL waterUrl = new URL(watermarkText);
URLConnection waterConnection = waterUrl.openConnection();
InputStream waterInputStream = waterConnection.getInputStream();
// 临时文件
String waterUrlPath = waterUrl.getPath();
File waterSrcFile = new File(waterUrlPath);
String waterSrcFileName = waterSrcFile.getName();
File watermarkImage = new File(waterSrcFileName);//水印临时文件
FileOutputStream waterOutputStream = new FileOutputStream(watermarkImage);
byte[] waterBuffer = new byte[4096];
int waterBytesRead;
while ((waterBytesRead = waterInputStream.read(waterBuffer)) != -1) {
waterOutputStream.write(waterBuffer, 0, waterBytesRead);
}
waterOutputStream.flush();
waterOutputStream.close();
waterInputStream.close();
//将文件名md5加密
MessageDigest md = MessageDigest.getInstance("MD5");
// 将输入字符串转换为字节数组
byte[] inputBytes = fileName.getBytes();
// 计算MD5摘要
byte[] hash = md.digest(inputBytes);
// 将字节数组转换为十六进制字符串
//加密后的文件名
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
//如果没有传入生成后的地址,在在源目录下保存生成后的水印视频
if (StringUtils.isBlank(outputFilePath)) {
outputFilePath = hexString + NEW_VIDOE_WATER_NAME_PRE_STR + ".mp4";
}
File inputVideo = new File(fileLinkUrl);
File outputVideo = new File(outputFilePath);
// File watermarkImage = new File(watermarkText);
/*
// 创建转码器
it.sauronsoftware.jave.Encoder encoder = new it.sauronsoftware.jave.Encoder();
// 创建转码参数
it.sauronsoftware.jave.EncodingAttributes attributes = new it.sauronsoftware.jave.EncodingAttributes();
attributes.setFormat("mp4");
VideoAttributes videoAttributes = new VideoAttributes();
videoAttributes.setCodec("mpeg4");
videoAttributes.setBitRate(800000);
videoAttributes.setFrameRate(30);
attributes.setVideoAttributes(videoAttributes);
it.sauronsoftware.jave.AudioAttributes audioAttributes = new it.sauronsoftware.jave.AudioAttributes();
audioAttributes.setCodec("aac");
audioAttributes.setBitRate(128000);
audioAttributes.setChannels(2);
audioAttributes.setSamplingRate(44100);
attributes.setAudioAttributes(audioAttributes);*/
// 创建转码器
Encoder encoder = new Encoder();
// 创建转码参数
EncodingAttributes attributes = new EncodingAttributes();
attributes.setOffset(1000F);
VideoAttributes videoAttributes = new VideoAttributes();
videoAttributes.setCodec("mpeg4");
videoAttributes.setBitRate(800000);
videoAttributes.setFrameRate(30);
attributes.setVideoAttributes(videoAttributes);
AudioAttributes audioAttributes = new AudioAttributes();
audioAttributes.setCodec("aac");
audioAttributes.setBitRate(128000);
audioAttributes.setChannels(2);
audioAttributes.setSamplingRate(44100);
attributes.setAudioAttributes(audioAttributes);
// 添加水印
try {
// 获取视频的宽度和高度
ProcessBuilder ffprobeBuilder = new ProcessBuilder("ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height", "-of", "csv=s=x:p=0", inputVideo.getAbsolutePath());
Process ffprobeProcess = ffprobeBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(ffprobeProcess.getInputStream()));
String line = reader.readLine();
int videoWidth = Integer.parseInt(line.split("x")[0]);
int videoHeight = Integer.parseInt(line.split("x")[1]);
// 获取水印的宽度和高度
BufferedImage watermark = ImageIO.read(watermarkImage);
int watermarkWidth = watermark.getWidth();
int watermarkHeight = watermark.getHeight();
// 计算水印的位置坐标
int x = (videoWidth - watermarkWidth) / 2;
int y = (videoHeight - watermarkHeight) / 2;
ProcessBuilder processBuilder = new ProcessBuilder("ffmpeg", "-i", inputVideo.getAbsolutePath(), "-i", watermarkImage.getAbsolutePath(),
"-filter_complex", "overlay=" + x + ":" + y, "-c:a", "copy", outputVideo.getAbsolutePath());
// ProcessBuilder processBuilder = new ProcessBuilder(
// "ffmpeg", "-i", inputVideo.getAbsolutePath(), "-i", watermarkImage.getAbsolutePath(), "-filter_complex", "overlay=W-w-10:H-h-10", "-c:a", "copy", outputVideo.getAbsolutePath());
processBuilder.inheritIO();
Process process = processBuilder.start();
process.waitFor();
System.out.println("视频添加水印成功!");
//将处理后的视频存入minio服务器中
amazonS3.putObject(minioConfig.getBucketName(), year + "/" + month + "/" + day + "/" + outputFilePath, outputVideo);
String[] split = inputFilePath.split("/", 5);
String s = split[2];
String replace = inputFilePath.replace(fileName, outputFilePath);
System.out.println("replace = " + replace);
return replace;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
System.out.println("视频添加水印失败!");
} finally {
//删除临时文件
deleteTempFile(srcFile);
deleteTempFile(outputVideo);
deleteTempFile(watermarkImage);
}
return null;
}
/**
* 视频转为gif
*/
/**
* 获得转化后的文件名
*
* @param sourceFilePath 源视频文件路径
* @return java.lang.String
* @author cyh
* @date 2022/11/12 17:01
*/
public String getNewFileName(String sourceFilePath) {
File source = new File(sourceFilePath);
String fileName = source.getName().substring(0, source.getName().lastIndexOf("."));
return fileName + "." + outputFormat;
}
/**
* 转化音频格式
*
* @param sourceFilePath 源视频文件路径
* @param targetFilePath 目标gif文件路径
* @return void
* @author cyh
* @date 2022/11/12 17:00
*/
public void transform(String sourceFilePath, String targetFilePath) {
File source = new File(sourceFilePath);
File target = new File(targetFilePath);
try {
//获得原视频的分辨率
MultimediaObject mediaObject = new MultimediaObject(source);
MultimediaInfo multimediaInfo = mediaObject.getInfo();
VideoInfo videoInfo = multimediaInfo.getVideo();
VideoSize sourceSize = videoInfo.getSize();
//设置视频属性
ws.schild.jave.encode.VideoAttributes video = new ws.schild.jave.encode.VideoAttributes();
video.setCodec(outputFormat);
//设置视频帧率 正常为10 ,值越大越流畅
video.setFrameRate(10);
//设置视频分辨率
VideoSize targetSize = new VideoSize(sourceSize.getWidth() / 5, sourceSize.getHeight() / 5);
video.setSize(targetSize);
//设置转码属性
EncodingAttributes attrs = new EncodingAttributes();
attrs.setVideoAttributes(video);
// 音频转换格式类
Encoder encoder = new Encoder();
encoder.encode(mediaObject, target, attrs);
System.out.println("= =转换成功= =");
} catch (EncoderException e) {
e.printStackTrace();
}
}
/**
* 批量转化视频格式
*
* @param sourceFolderPath 源视频文件夹路径
* @param targetFolderPath 目标gif文件夹路径
* @return void
* @author cyh
* @date 2022/11/12 17:00
*/
public String batchTransform(String sourceFolderPath, String targetFolderPath) throws IOException {
//获取文件名
URL url = new URL(sourceFolderPath);
// 临时视频文件
URLConnection connection = url.openConnection();
InputStream inputStream = connection.getInputStream();
// 从src的url中获取到要切的文件名、日期目录
URL fileUrl = new URL(sourceFolderPath);
File file = new File(fileUrl.getPath());
String fileName = file.getName();
String path = file.getPath();
String date = path.substring(path.lastIndexOf("_") + 1, path.lastIndexOf("_") + 9);
String year = date.substring(0, 4);
String month = date.substring(4, 6);
String day = date.substring(6, 8);
// 临时文件
File srcFile = new File(fileName);
FileOutputStream outputStream = new FileOutputStream(srcFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
//获取下载的路径
String fileLinkUrl = srcFile.getPath();//下载后的本地文件地址
//如果没有传入生成后的地址,在在源目录下保存生成后的水印视频
if (StringUtils.isBlank(targetFolderPath)) {
targetFolderPath = fileLinkUrl.substring(0, fileLinkUrl.lastIndexOf(".")) + "_Glf" + ".gif";
}
File outPutGlf = new File(targetFolderPath);
transform(fileLinkUrl, targetFolderPath);
//将生成后的gif文件上传到minio服务中
amazonS3.putObject(minioConfig.getBucketName(), year + "/" + month + "/" + day + "/" + targetFolderPath, outPutGlf);
String replace = sourceFolderPath.replace(fileName, targetFolderPath);
System.out.println("replace = " + replace);
//删除临时文件
deleteTempFile(srcFile);
deleteTempFile(outPutGlf);
return replace;
}
/**
* 视频裁剪
*
* @param src
* @return
* @throws EncoderException
* @throws IOException
*/
public String cutvideo(String src) throws EncoderException, IOException {
File srcFile = null;
File targetFile = null;
try {
URL url = new URL(src);
URLConnection connection = url.openConnection();
InputStream inputStream = connection.getInputStream();
File file = new File(url.getPath());
String fileName = file.getName();
int dotIndex = fileName.lastIndexOf(".");
String suffix = fileName.substring(dotIndex);//后缀名
//获取日期
int startIndex = src.indexOf("/bigdatacenter/") + "/bigdatacenter/".length();
int endIndex = startIndex + 10; // 日期字符串长度为8
String date = src.substring(startIndex, endIndex);
String year = date.substring(0, 4);
String month = date.substring(5, 7);
String day = date.substring(8, 10);
//构建源文件的临时文件
srcFile = new File(fileName.substring(0, dotIndex - 1) + "temp" + suffix);
FileOutputStream outputStream = new FileOutputStream(srcFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
// 裁切的文件名拼接上_cutAudio。
String target = "cutVideo_" + fileName.substring(0, dotIndex) + suffix;
// 裁剪的目标文件
targetFile = new File(target);
MultimediaObject srcMultiObj = new MultimediaObject(srcFile);
MultimediaInfo srcMediaInfo = srcMultiObj.getInfo();
// 拿到原音频的时长(毫秒)
long duration = srcMediaInfo.getDuration();
long mediaSecond = 0;
if (duration > 0) {
// 转换为秒
mediaSecond = duration / 1000;
}
it.sauronsoftware.jave.EncodingAttributes encodingAttributes = new it.sauronsoftware.jave.EncodingAttributes();
// 大于60秒小于3分钟 剪切60秒;大于3分钟剪切1分钟
if (mediaSecond > 60) {
// 设置起始偏移量(秒)
encodingAttributes.setOffset(0.0F);
// 设置切片的音频长度(秒)
encodingAttributes.setDuration(30.0F);
} else if (duration <= 60) {
// 设置起始偏移量(秒)
encodingAttributes.setOffset(0.0F);
// 设置切片的音频长度(秒)
encodingAttributes.setDuration(Float.valueOf(duration / 2));
}
encodingAttributes.setFormat("mp4");
it.sauronsoftware.jave.VideoAttributes videoAttributes = new it.sauronsoftware.jave.VideoAttributes();
videoAttributes.setCodec("mpeg4");
videoAttributes.setBitRate(srcMediaInfo.getVideo().getBitRate());// 设置视频比特率
videoAttributes.setFrameRate((int) srcMediaInfo.getVideo().getFrameRate());// 设置视频帧率
encodingAttributes.setVideoAttributes(videoAttributes);
/* it.sauronsoftware.jave.AudioAttributes audioAttributes = new it.sauronsoftware.jave.AudioAttributes();
String audioCodec = srcMediaInfo.getAudio().getDecoder().split(" ")[0];
audioAttributes.setCodec("MP3");
audioAttributes.setBitRate(128000);
audioAttributes.setChannels(2);
audioAttributes.setSamplingRate(44100);
encodingAttributes.setAudioAttributes(audioAttributes);*/
// Encoder encoder = new Encoder();
it.sauronsoftware.jave.Encoder encoder = new it.sauronsoftware.jave.Encoder();
encoder.encode(srcMultiObj.getFile(), targetFile, encodingAttributes); // 执行裁剪操作
System.out.println("视频裁剪完成");
// 将裁剪后的文件保存到 Amazon S3
amazonS3.putObject(minioConfig.getBucketName(), year + "/" + month + "/" + day + "/" + target, targetFile);
String replace = src.replace(fileName, target);
System.out.println("replace = " + replace);
return replace;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 在 finally 块中删除临时文件
deleteTempFile(srcFile);
deleteTempFile(targetFile);
}
}
public Integer getVideoDuration(String fileLink) {
int durationInSeconds = 0;
try {
ProcessBuilder ffprobeBuilder = new ProcessBuilder("ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", fileLink);
Process process = ffprobeBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String durationString = reader.readLine();
double durationInMilliseconds = Double.parseDouble(durationString) * 1000;
durationInSeconds = (int) TimeUnit.MILLISECONDS.toSeconds((long) durationInMilliseconds);
process.waitFor();
reader.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return durationInSeconds;
}
/**
* 获取存储地址(上传生产端时使用)
*
* @param fileUrl
* @return
*/
public String getFilePath(String fileUrl) throws MalformedURLException, NoSuchAlgorithmException {
// 获取文件后缀
String fileSuffix = Paths.get(new URL(fileUrl).getPath()).getFileName().toString().substring(Paths.get(new URL(fileUrl).getPath()).getFileName().toString().lastIndexOf("."));
// 获取当前日期
LocalDateTime now = LocalDateTime.now();
// 格式化日期时间
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String currentDate = now.format(formatter);
// MD5加密文件名
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bytes = md5.digest(Paths.get(new URL(fileUrl).getPath()).getFileName().toString().getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
String fileName = sb.toString();
// 拼接最终结果
return currentDate + "/" + fileName + fileSuffix;
}
/**
* 从本地存储服务器上传到生产端存储服务器
*
* @param fileUrl 上传的文件url
* @param filePath 上传到生产端时的路径
* @return 上传到生产端后的路径
* @throws MalformedURLException
*/
public String transferFile(String fileUrl, String filePath) throws MalformedURLException {
File file = null;
try {
// 临时文件路径
String tempFilePath = localFilePath + File.separator + filePath;
PresignedUrlDownloadRequest request = new PresignedUrlDownloadRequest(new URL(fileUrl));
file = new File(tempFilePath);
// 从本地minio服务器中下载到本地临时路径
amazonS3.download(request, file);
// 返回上传的第二个minio服务器地址
//文件大于5MB 执行分片上传
if (file.length() > 5 * 1024 * 1024) {
FileUploadCenterParam fileUploadCenterParam = new FileUploadCenterParam();
fileUploadCenterParam.setFileUrl(fileUrl);
fileUploadCenterParam.setFilePath(filePath);
fileUploadCenterParam.setBucketName(centerMinioConfig.getBucketName());
this.multipartUploadData(centerAmazonS3Client, fileUploadCenterParam, tempFilePath);
} else {
centerAmazonS3Client.putObject(centerMinioConfig.getBucketName(), filePath, file);
}
// 返回:上传到生产端的文件地址
return centerMinioConfig.getViewUrl() + "/" + centerMinioConfig.getBucketName() + "/" + filePath;
} finally {
// 删除本地临时文件
deleteTempFile(file);
}
}
/**
* 获取上传到生产端存储服务器的地址
*
* @param filePath 上传到生产端时的路径
* @return 上传到生产端后的路径
* @throws MalformedURLException
*/
public String getCenterFileUrl(String filePath) {
// 返回:上传到生产端的文件地址
return centerMinioConfig.getViewUrl() + "/" + centerMinioConfig.getBucketName() + "/" + filePath;
}
/**
* 分片上传资源
*
* @param amazonS3 s3存储对象
* @param fileUploadCenterParam 文件封装对象
* @param tempFilePath 下载的临时路径
* @author wangjinfei
* @date 2024/2/19 10:11
*/
private void multipartUploadData(AmazonS3 amazonS3, FileUploadCenterParam fileUploadCenterParam, String tempFilePath) {
System.out.println("开始分片上传");
// 设置分片大小(5MB)
long chunkSize = 5 * 1024 * 1024;
String bucketName = fileUploadCenterParam.getBucketName();
String keyName = fileUploadCenterParam.getFilePath();
// 初始化分片上传请求
InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(bucketName, keyName);
InitiateMultipartUploadResult initResult = amazonS3.initiateMultipartUpload(initiateRequest);
String uploadId = initResult.getUploadId();
// 上传文件分片
List<PartETag> partETags = new ArrayList<>();
long filePosition = 0;
try {
File file = new File(tempFilePath);
long contentLength = file.length();
for (int i = 1; filePosition < contentLength; i++) {
System.out.println("i = " + i);
// 计算分片大小
long remainingBytes = contentLength - filePosition;
long bytesToRead = Math.min(chunkSize, remainingBytes);
// 读取分片数据
byte[] bytes = new byte[(int) bytesToRead];
RandomAccessFile fileInputStream = new RandomAccessFile(file, "r");
fileInputStream.seek(filePosition);
fileInputStream.read(bytes);
fileInputStream.close();
// 上传分片
UploadPartRequest uploadRequest = new UploadPartRequest()
.withBucketName(bucketName)
.withKey(keyName)
.withUploadId(uploadId)
.withPartNumber(i)
.withFileOffset(filePosition)
.withFile(file)
.withPartSize(bytesToRead)
.withInputStream(new ByteArrayInputStream(bytes));
UploadPartResult uploadPartResult = amazonS3.uploadPart(uploadRequest);
partETags.add(uploadPartResult.getPartETag());
// 更新文件位置
filePosition += bytesToRead;
}
// 完成分片上传
CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(bucketName, keyName, uploadId, partETags);
amazonS3.completeMultipartUpload(completeRequest);
System.out.println("完成分片上传");
} catch (Exception e) {
e.printStackTrace();
log.error("分片上传数据柜存储服务器出错:{}", e.getMessage());
}
}
/**
* 压缩图片并上传
*/
public CompressCoverLink compressAndUploadImage(String imageURL) {
if (ObjectUtil.isEmpty(imageURL)) {
return null;
}
try {
// 从URL中获取文件名
String fileName = imageURL.substring(imageURL.lastIndexOf('/') + 1);
// 从文件名中获取文件扩展名
String fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
// 如果为gif则不压缩图片
if ("gif".equalsIgnoreCase(fileExtension)) {
URL gifUrl = new URL(imageURL);
HttpURLConnection connection = (HttpURLConnection) gifUrl.openConnection();
connection.setRequestMethod("GET");
// GIF的第一帧
BufferedImage firstFrame = ImageIO.read(connection.getInputStream());
// 创建一个新的RGB颜色模型的BufferedImage
BufferedImage newImage = new BufferedImage(firstFrame.getWidth(), firstFrame.getHeight(), BufferedImage.TYPE_INT_RGB);
newImage.createGraphics().drawImage(firstFrame, 0, 0, null);
// 将BufferedImage转换为字节数组
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(newImage, "jpg", os);
byte[] imageData = os.toByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData);
// 生成上传minion的key
UUID uuid = UUID.randomUUID();
String uuidString = uuid.toString();
LocalDate currentDate = LocalDate.now();
String dateString = currentDate.toString();
String objectKey = dateString + "/" + uuidString + ".jpg";
centerAmazonS3Client.putObject(centerMinioConfig.getBucketName(), objectKey, byteArrayInputStream, null);
// 将截取出来的图片赋值,通过下面代码进行图片截取
imageURL = centerMinioConfig.getViewUrl() + "/" + centerMinioConfig.getBucketName() + "/" + objectKey;
}
// 下载远程图片到内存
URL url = new URL(imageURL);
BufferedImage originalImage = ImageIO.read(url);
int originalWidth = originalImage.getWidth();
int originalHeight = originalImage.getHeight();
// 将压缩后的图片转为字节数组
ByteArrayOutputStream os = new ByteArrayOutputStream();
if (originalWidth > 720) {
// 压缩图片
BufferedImage compressedImage = Thumbnails.of(originalImage)
.size(720, (int) (originalImage.getHeight() * (720.0 / originalImage.getWidth()))) // 设置压缩后的尺寸
.asBufferedImage(); // 获取压缩后的BufferedImage
ImageIO.write(compressedImage, "jpg", os);
originalWidth = 720;
originalHeight = (int) (originalImage.getHeight() * (720.0 / originalImage.getWidth()));
} else {
ImageIO.write(originalImage, "jpg", os);
}
byte[] imageData = os.toByteArray();
InputStream inputStream = new ByteArrayInputStream(imageData);
// 生成UUID
UUID uuid = UUID.randomUUID();
String uuidString = uuid.toString();
LocalDate currentDate = LocalDate.now();
String dateString = currentDate.toString();
String objectKey = dateString + "/" + uuidString + ".jpg";
// 上传压缩后的图片到MinIO
centerAmazonS3Client.putObject(centerMinioConfig.getBucketName(), objectKey, inputStream, null);
String filePath = centerMinioConfig.getViewUrl() + "/" + centerMinioConfig.getBucketName() + "/" + objectKey;
return new CompressCoverLink(originalWidth, originalHeight, filePath);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 视频剪切第10秒的图片
*
* @return
*/
public String videoCutCoverLink(String videoLink) {
// 取第10s的图片
String timePoint = "00:00:10";
try {
FfmpegCmd ffmpegCmd = new FfmpegCmd();
/**
* 错误流
*/
try {
String cmdStr = "-i " + videoLink + " -ss " + timePoint + " -vframes 1 -f image2 -";
ffmpegCmd.execute(false, true, cmdStr);
InputStream inputStream = ffmpegCmd.getInputStream();
UUID uuid = UUID.randomUUID();
String uuidString = uuid.toString();
LocalDate currentDate = LocalDate.now();
String dateString = currentDate.toString();
String objectKey = dateString + "/" + uuidString + ".jpg";
centerAmazonS3Client.putObject(centerMinioConfig.getBucketName(), objectKey, inputStream, null);
//code=0表示正常
ffmpegCmd.getProcessExitCode();
String filePath = centerMinioConfig.getViewUrl() + "/" + centerMinioConfig.getBucketName() + "/" + objectKey;
return filePath;
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
ffmpegCmd.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}