【将视频链接转换为m3u8链接】将视频转换为m3u8格式再上传到远程服务器

将视频文件(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;
    }
}

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打乒乓球只会抽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值