当前位置:   article > 正文

ffmpeg封装工具_ffmpeg java封装库

ffmpeg java封装库

格式转换/下载视频

ffmpeg -i index.m3u8 -c copy -y test.mp4
  • 1
  • -i表示输入源,可以是本地路径,也可以是网络地址
  • -c表示复制视频编码
  • -y表示强制覆盖本地文件

使用显卡提速

通过添加 -vcodec h264_nvenc 来提速

Java工具类封装

封装了常见的方法:获取视频信息、截图、剪切、生成预览视频、格式转换等。
这里面用到了ws.schild.jave的一些方法,需要引入依赖:

<dependency>
   <groupId>ws.schild</groupId>
    <artifactId>jave-all-deps</artifactId>
    <version>3.3.1</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

FfmpegJob:

package mt.spring.tools.video.ffmpeg;

import lombok.extern.slf4j.Slf4j;
import ws.schild.jave.ConversionOutputAnalyzer;
import ws.schild.jave.EncoderException;
import ws.schild.jave.process.ProcessLocator;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Pattern;

/**
 * @Author Martin
 * @Date 2021/2/3
 */
@Slf4j
public class FfmpegJob {
	public final static ProcessLocator locator = new DefaultFFMPEGLocator();
	
	private static final Pattern SUCCESS_PATTERN = Pattern.compile("^\\s*video\\:\\S+\\s+audio\\:\\S+\\s+subtitle\\:\\S+\\s+global headers\\:\\S+.*$", Pattern.CASE_INSENSITIVE);
	
	public interface FfmpegWorker {
		void addArguments(ProcessWrapper ffmpeg);
	}
	
	public static void execute(FfmpegWorker ffmpegWorker) {
		ProcessWrapper ffmpeg = locator.createExecutor();
		ffmpegWorker.addArguments(ffmpeg);
		try {
			ffmpeg.execute();
			try (RBufferedReader reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
				String line;
				ConversionOutputAnalyzer outputAnalyzer = new ConversionOutputAnalyzer(0, null);
				while ((line = reader.readLine()) != null) {
					outputAnalyzer.analyzeNewLine(line);
				}
				if (outputAnalyzer.getLastWarning() != null) {
					String lastWarning = outputAnalyzer.getLastWarning();
					if (!SUCCESS_PATTERN.matcher(lastWarning).matches()) {
						throw new RuntimeException("No match for: " + SUCCESS_PATTERN + " in " + lastWarning);
					}
				}
			}
			int exitCode = ffmpeg.getProcessExitCode();
			if (exitCode != 0) {
				log.error("Process exit code: {}", exitCode);
				throw new RuntimeException("Exit code of ffmpeg encoding run is " + exitCode);
			}
		} catch (IOException | EncoderException e) {
			throw new RuntimeException(e);
		} finally {
			ffmpeg.destroy();
		}
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

FfmpegUtils:

package mt.spring.tools.video;

import lombok.extern.slf4j.Slf4j;
import mt.spring.tools.video.ffmpeg.FfmpegJob;
import mt.utils.common.Assert;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.ScreenExtractor;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;
import ws.schild.jave.info.VideoSize;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @Author Martin
 * @Date 2020/12/10
 */
@Slf4j
public class FfmpegUtils {
	
	/**
	 * 获取视频信息
	 *
	 * @param object   媒体文件
	 * @param timeout  超时
	 * @param timeUnit 超时单位
	 * @return 视频信息
	 * @throws Exception 异常
	 */
	public static mt.spring.tools.video.entity.VideoInfo getVideoInfo(MultimediaObject object, long timeout, TimeUnit timeUnit) throws Exception {
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		try {
			Future<mt.spring.tools.video.entity.VideoInfo> future = singleThreadExecutor.submit(() -> {
				MultimediaInfo info = object.getInfo();
				VideoInfo video = info.getVideo();
				Assert.notNull(video, "video parsed error");
				mt.spring.tools.video.entity.VideoInfo videoInfo = new mt.spring.tools.video.entity.VideoInfo();
				long duration = info.getDuration();
				videoInfo.setDuring(duration);
				if (duration > 0) {
					videoInfo.setVideoLength(secondToTime(duration / 1000));
				}
				videoInfo.setFormat(info.getFormat());
				videoInfo.setWidth(video.getSize().getWidth());
				videoInfo.setHeight(video.getSize().getHeight());
				videoInfo.setBitRate(video.getBitRate());
				videoInfo.setFrameRate(video.getFrameRate());
				videoInfo.setDecoder(video.getDecoder());
				return videoInfo;
			});
			return future.get(timeout, timeUnit);
		} finally {
			singleThreadExecutor.shutdownNow();
		}
	}
	
	/**
	 * 获取视频信息
	 *
	 * @param source   源文件
	 * @param timeout  超时
	 * @param timeUnit 超时单位
	 * @return 视频信息
	 * @throws Exception 异常
	 */
	public static mt.spring.tools.video.entity.VideoInfo getVideoInfo(File source, long timeout, TimeUnit timeUnit) throws Exception {
		return getVideoInfo(new MultimediaObject(source), timeout, timeUnit);
	}
	
	/**
	 * 获取视频信息
	 *
	 * @param url      视频地址
	 * @param timeout  超时
	 * @param timeUnit 超时单位
	 * @return 视频信息
	 * @throws Exception 异常
	 */
	public static mt.spring.tools.video.entity.VideoInfo getVideoInfo(URL url, long timeout, TimeUnit timeUnit) throws Exception {
		return getVideoInfo(new MultimediaObject(url), timeout, timeUnit);
	}
	
	/**
	 * 获取视频长度
	 *
	 * @param url 视频地址
	 * @return 视频长度
	 * @throws MalformedURLException 异常
	 * @throws EncoderException      异常
	 */
	public static String getVideoLength(String url) throws MalformedURLException, EncoderException {
		URL url1 = new URL(url);
		MultimediaObject object = new MultimediaObject(url1);
		MultimediaInfo info = object.getInfo();
		long duration = info.getDuration();
		return secondToTime(duration / 1000);
	}
	
	/**
	 * 获取视频长度
	 *
	 * @param file 文件
	 * @return 视频长度
	 * @throws MalformedURLException 异常
	 * @throws EncoderException      异常
	 */
	public static String getVideoLength(File file) throws MalformedURLException, EncoderException {
		MultimediaObject object = new MultimediaObject(file);
		MultimediaInfo info = object.getInfo();
		long duration = info.getDuration();
		return secondToTime(duration / 1000);
	}
	
	/**
	 * 将时间转换成00:00格式
	 *
	 * @param second 秒
	 * @return 转换结果
	 */
	public static String secondToTime(long second) {
		DecimalFormat format = new DecimalFormat("00");
		long days = second / 86400;            //转换天数
		second = second % 86400;            //剩余秒数
		long hours = second / 3600;            //转换小时
		second = second % 3600;                //剩余秒数
		long minutes = second / 60;            //转换分钟
		second = second % 60;                //剩余秒数
		String dd = format.format(days);
		String HH = format.format(hours);
		String mm = format.format(minutes);
		String ss = format.format(second);
		StringBuilder result = new StringBuilder();
		if (days > 0) {
			result.append(":").append(dd);
		}
		if (hours > 0) {
			result.append(":").append(HH);
		}
		result.append(":").append(mm);
		result.append(":").append(ss);
		return result.substring(1, result.length());
	}
	
	/**
	 * 截图
	 *
	 * @param srcFile 源文件
	 * @param desFile 目标文件
	 * @param width   宽度
	 * @param seconds 第几秒
	 * @throws Exception 异常
	 */
	public static void screenShot(File srcFile, File desFile, int width, int seconds) throws Exception {
		screenShot(new MultimediaObject(srcFile), desFile, width, seconds, 60, TimeUnit.SECONDS);
	}
	
	/**
	 * 截图
	 *
	 * @param url     视频地址
	 * @param desFile 目标文件
	 * @param width   宽度
	 * @param seconds 第几秒
	 * @throws Exception 异常
	 */
	public static void screenShot(URL url, File desFile, int width, int seconds) throws Exception {
		screenShot(new MultimediaObject(url), desFile, width, seconds, 60, TimeUnit.SECONDS);
	}
	
	/**
	 * 截图
	 *
	 * @param object   媒体文件
	 * @param desFile  目标文件
	 * @param width    宽度
	 * @param seconds  第几秒
	 * @param timeout  超时
	 * @param timeUnit 超时单位
	 * @throws Exception 异常
	 */
	public static void screenShot(MultimediaObject object, File desFile, int width, final int seconds, long timeout, TimeUnit timeUnit) throws Exception {
		if (desFile.exists()) {
			return;
		}
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		try {
			Future<?> submit = singleThreadExecutor.submit(() -> {
				try {
					File parentFile = desFile.getParentFile();
					if (!parentFile.exists()) {
						parentFile.mkdirs();
					}
					double maxSeconds = 0;
					int s = seconds;
					if (s > 0) {
						try {
							long duration = object.getInfo().getDuration();
							maxSeconds = Math.floor(duration * 1.0 / 1000) - 5;
							if (maxSeconds < 0) {
								maxSeconds = 0;
							}
						} catch (Exception ignored) {
							s = 0;
						}
					}
					ScreenExtractor screenExtractor = new ScreenExtractor();
					VideoSize size = object.getInfo().getVideo().getSize();
					int height = (int) Math.ceil(width * size.getHeight() * 1.0 / size.getWidth());
					screenExtractor.render(object, width, height, (int) Math.min(maxSeconds, s), desFile, 1);
				} catch (Exception e) {
					log.error(e.getMessage(), e);
					throw new RuntimeException(e);
				}
			});
			submit.get(timeout, timeUnit);
		} finally {
			singleThreadExecutor.shutdownNow();
		}
	}
	
	/**
	 * 连续截图20分钟
	 *
	 * @param srcFile 源文件
	 * @param dstPath 目标目录
	 */
	public static void screenshotsTwentyMinutes(@NotNull File srcFile, @NotNull File dstPath) {
		screenshots(srcFile, dstPath, 0.01667, "00:00", "20:00", 400);
	}
	
	/**
	 * 连续截图
	 * ffmpeg -ss 00:00 -i 5.mp4 -f image2 -r 0.01667 -t 20:00 -filter:v scale=400:-1 thumb/%3d.jpg
	 *
	 * @param srcFile    源文件
	 * @param dstPath    目标目录
	 * @param rate       每秒播放的帧  1 = 间隔秒数 * rate,例如5秒截图一次,那就是rate = 0.2
	 * @param startTime  开始时间,格式xx:xx,例如00:00
	 * @param duringRime 持续时间,格式xx:xx,例如20:00
	 * @param width      宽度
	 */
	public static void screenshots(@NotNull File srcFile, @NotNull File dstPath, double rate, @NotNull String startTime, @NotNull String duringRime, int width) {
		dstPath.mkdirs();
		FfmpegJob.execute(ffmpeg -> {
			ffmpeg.addArgument("-ss");
			ffmpeg.addArgument(startTime);
			ffmpeg.addArgument("-i");
			ffmpeg.addArgument(srcFile.getAbsolutePath());
			ffmpeg.addArgument("-f");
			ffmpeg.addArgument("image2");
			ffmpeg.addArgument("-r");
			ffmpeg.addArgument(rate + "");
			ffmpeg.addArgument("-t");
			ffmpeg.addArgument(duringRime);
			ffmpeg.addArgument("-filter:v");
			ffmpeg.addArgument("scale=" + width + ":-1");
			ffmpeg.addArgument(dstPath.getAbsolutePath() + "/%3d.jpg");
		});
	}
	
	/**
	 * 压缩图片
	 *
	 * @param srcFile 源文件
	 * @param desFile 目标文件
	 * @param width   宽度
	 * @throws Exception 异常
	 */
	public static void compressImage(File srcFile, File desFile, int width) throws Exception {
		screenShot(srcFile, desFile, width, 0);
	}
	
	/**
	 * 剪切视频
	 * 命令:ffmpeg -i 1.mp4 -ss 00:00:00 -to 00:00:20 -y -f mp4 -vcodec copy -acodec copy -q:v 1 thumb.mp4
	 *
	 * @param srcFile 源文件
	 * @param desFile 目标文件
	 * @param from    从,例:00:00:00
	 * @param to      到,例:00:00:20
	 */
	public static void cutVideo(@NotNull File srcFile, @NotNull File desFile, @NotNull String from, @NotNull String to, @Nullable String vCodec) {
		if (StringUtils.isBlank(vCodec)) {
			vCodec = "copy";
		}
		String finalVCodec = vCodec;
		FfmpegJob.execute(ffmpeg -> {
			ffmpeg.addArgument("-i");
			ffmpeg.addArgument(srcFile.getAbsolutePath());
			ffmpeg.addArgument("-ss");
			ffmpeg.addArgument(from);
			ffmpeg.addArgument("-to");
			ffmpeg.addArgument(to);
			ffmpeg.addArgument("-y");
			ffmpeg.addArgument("-f");
			ffmpeg.addArgument("mp4");
			ffmpeg.addArgument("-vcodec");
			ffmpeg.addArgument(finalVCodec);
			ffmpeg.addArgument("-acodec");
			ffmpeg.addArgument("copy");
			ffmpeg.addArgument("-q:v");
			ffmpeg.addArgument("1");
			ffmpeg.addArgument(desFile.getAbsolutePath());
		});
	}
	
	/**
	 * 生成预览视频
	 * 命令:ffmpeg -i 1.mp4 -vf "select='lte(mod(t, 122),1)',scale=400:-2,setpts=N/FRAME_RATE/TB" -an -y preview.mp4
	 *
	 * @param srcFile  源文件
	 * @param dstFile  目标文件,例如:preview.mp4
	 * @param segments 分段,每段1秒
	 * @param width    宽度
	 * @return 是否生成
	 * @throws Exception 异常
	 */
	public static boolean generatePreviewVideo(@NotNull File srcFile, @NotNull File dstFile, int segments, int width, @Nullable String vCodec) throws Exception {
		return generatePreviewVideo(srcFile, dstFile, segments, width, -2, vCodec);
	}
	
	/**
	 * 生成预览视频
	 * 命令:ffmpeg -i 1.mp4 -vf "select='lte(mod(t, 122),1)',scale=400:-2,setpts=N/FRAME_RATE/TB" -an -y preview.mp4
	 * F
	 *
	 * @param srcFile  源文件
	 * @param dstFile  目标文件,例如:preview.mp4
	 * @param segments 分段,每段1秒
	 * @param width    宽度
	 * @param height   高度
	 * @return 是否生成
	 * @throws Exception 异常
	 */
	public static boolean generatePreviewVideo(@NotNull File srcFile, @NotNull File dstFile, int segments, int width, int height, @Nullable String vCodec) throws Exception {
		mt.spring.tools.video.entity.VideoInfo videoInfo = getVideoInfo(srcFile, 1, TimeUnit.MINUTES);
		long during = videoInfo.getDuring();
		long second = during / 1000 / segments;
		if (second > segments) {
			FfmpegJob.execute(ffmpeg -> {
				ffmpeg.addArgument("-i");
				ffmpeg.addArgument(srcFile.getAbsolutePath());
				if (StringUtils.isNotBlank(vCodec)) {
					ffmpeg.addArgument("-vcodec");
					ffmpeg.addArgument(vCodec);
				}
				ffmpeg.addArgument("-vf");
				ffmpeg.addArgument("\"select='lte(mod(t, " + second + "),1)',scale=" + width + ":" + height + ",setpts=N/FRAME_RATE/TB\"");
				ffmpeg.addArgument("-an");
				ffmpeg.addArgument("-y");
				ffmpeg.addArgument(dstFile.getAbsolutePath());
			});
			return true;
		}
		log.info("视频长度小于分片长度,不能生成预览视频,segments={}", segments);
		return false;
	}
	
	/**
	 * 转换格式
	 * ffmpeg -i 1.wmv -y 1.mp4
	 *
	 * @param srcFile 源文件
	 * @param dstFile 目标文件
	 */
	public static void convert(@NotNull File srcFile, @NotNull File dstFile, @Nullable String vCodec) {
		FfmpegJob.execute(ffmpeg -> {
			ffmpeg.addArgument("-i");
			ffmpeg.addArgument(srcFile.getAbsolutePath());
			if (StringUtils.isNotBlank(vCodec)) {
				ffmpeg.addArgument("-vcodec");
				ffmpeg.addArgument(vCodec);
			}
			ffmpeg.addArgument("-vf");
			ffmpeg.addArgument("scale=iw:-2");
			ffmpeg.addArgument("-y");
			ffmpeg.addArgument(dstFile.getAbsolutePath());
		});
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392

m3u8相关的封装

package mt.spring.tools.video;

import lombok.extern.slf4j.Slf4j;
import mt.spring.tools.video.ffmpeg.FfmpegJob;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;

import java.io.File;
import java.util.concurrent.TimeUnit;

/**
 * @Author Martin
 * @Date 2021/2/3
 */
@Slf4j
public class HlsUtils {
	public static final int MB = 1024 * 1024;
	
	/**
	 * 将源文件转换成ts格式,并且分割成多个ts文件
	 *
	 * @param source            源文件
	 * @param target            目标文件 例:index.m3u8
	 * @param segmentMB         每段ts文件大小
	 * @param minSegmentSeconds 分段最小视频长度
	 */
	public static void convertToHlsBySize(File source, File target, int segmentMB, @Nullable Integer minSegmentSeconds, @Nullable String vCodec) {
		if (minSegmentSeconds == null) {
			minSegmentSeconds = 15;
		}
		//每个分片按10MB计算,但时长不能小于5s
		MultimediaObject object = new MultimediaObject(source);
		try {
			long duration = object.getInfo().getDuration();
			long length = source.length();
			double sizeMb = Math.ceil(length * 1.0 / MB);
			double perSecondMb = sizeMb / TimeUnit.MILLISECONDS.toSeconds(duration);
			int segmentSeconds = (int) Math.ceil(segmentMB * 1.0 / perSecondMb);
			if (segmentSeconds < minSegmentSeconds) {
				segmentSeconds = minSegmentSeconds;
			}
			convertToHlsBySeconds(source, target, segmentSeconds, vCodec);
		} catch (EncoderException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 将源文件转换成ts格式,并且分割成多个ts文件
	 *
	 * @param source         源文件
	 * @param target         目标文件 例:index.m3u8
	 * @param segmentSeconds 每段视频长度
	 * @param vCodec         视频编码
	 */
	public static void convertToHlsBySeconds(File source, File target, int segmentSeconds, @Nullable String vCodec) {
		File tsFile = new File(target.getParentFile(), target.getName() + ".ts");
		convertTs(source, tsFile, vCodec);
		splitTs(tsFile, target, segmentSeconds);
		tsFile.delete();
	}
	
	/**
	 * 将源文件转换成.ts格式
	 * 命令:ffmpeg -y -i "IMG_8308.MOV"  -vcodec copy -acodec copy -vbsf h264_mp4toannexb test.ts
	 *
	 * @param source 源文件,例如:IMG_8308.MOV
	 * @param target 目标文件,例如:test.ts
	 * @param vCodec 视频编码
	 */
	public static void convertTs(File source, File target, @Nullable String vCodec) {
		log.info("转换为ts文件:{}", source);
		File parentFile = target.getParentFile();
		if (!parentFile.exists()) {
			parentFile.mkdirs();
		}
		try {
			FfmpegJob.execute(ffmpeg -> {
				ffmpeg.addArgument("-y");
				ffmpeg.addArgument("-i");
				ffmpeg.addArgument(source.getAbsolutePath());
				ffmpeg.addArgument("-vf");
				ffmpeg.addArgument("scale=iw:-2");
				ffmpeg.addArgument("-vcodec");
				if (StringUtils.isNotBlank(vCodec)) {
					ffmpeg.addArgument(vCodec);
				} else {
					ffmpeg.addArgument("copy");
				}
				ffmpeg.addArgument("-acodec");
				ffmpeg.addArgument("copy");
				ffmpeg.addArgument("-vbsf");
				ffmpeg.addArgument("h264_mp4toannexb");
				ffmpeg.addArgument(target.getAbsolutePath());
			});
		} catch (Exception e) {
			FfmpegJob.execute(ffmpeg -> {
				ffmpeg.addArgument("-y");
				ffmpeg.addArgument("-i");
				ffmpeg.addArgument(source.getAbsolutePath());
				ffmpeg.addArgument("-vf");
				ffmpeg.addArgument("scale=iw:-2");
				ffmpeg.addArgument("-vcodec");
				ffmpeg.addArgument("h264");
				ffmpeg.addArgument("-acodec");
				ffmpeg.addArgument("copy");
				ffmpeg.addArgument("-vbsf");
				ffmpeg.addArgument("h264_mp4toannexb");
				ffmpeg.addArgument(target.getAbsolutePath());
			});
		}
	}
	
	/**
	 * 将源文件分割成多个ts文件
	 * 命令:ffmpeg -i test.ts -c copy -map 0 -f segment -segment_list test.m3u8 -segment_time 60 "60s_%3d.ts"
	 *
	 * @param source         源文件
	 * @param target         目标文件 例:index.m3u8
	 * @param segmentSeconds 每段视频长度
	 */
	public static void splitTs(File source, File target, @Nullable Integer segmentSeconds) {
		log.info("分割ts文件:{}", source);
		target.getParentFile().mkdirs();
		if (segmentSeconds == null) {
			segmentSeconds = 30;
		}
		Integer finalSegmentSeconds = segmentSeconds;
		FfmpegJob.execute(ffmpeg -> {
			ffmpeg.addArgument("-i");
			ffmpeg.addArgument(source.getAbsolutePath());
			ffmpeg.addArgument("-c");
			ffmpeg.addArgument("copy");
			ffmpeg.addArgument("-map");
			ffmpeg.addArgument("0");
			ffmpeg.addArgument("-f");
			ffmpeg.addArgument("segment");
			ffmpeg.addArgument("-segment_list");
			ffmpeg.addArgument(target.getAbsolutePath());
			ffmpeg.addArgument("-segment_time");
			ffmpeg.addArgument(finalSegmentSeconds + "");
			ffmpeg.addArgument(new File(target.getParentFile(), "segment_%3d.ts").getAbsolutePath());
		});
	}
	
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149

完整的代码

完整的代码我放到github代码库了:https://github.com/668mt/mt-spring-web.git
视频处理相关的代码在mt-tools/video-tools

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/742654
推荐阅读
相关标签
  

闽ICP备14008679号