├── .java-version ├── fmj.jpg ├── src ├── main │ └── java │ │ └── com │ │ └── github │ │ └── tonydeng │ │ └── fmj │ │ ├── handler │ │ ├── ProcessCallbackHandler.java │ │ └── DefaultCallbackHandler.java │ │ ├── model │ │ ├── HLS.java │ │ ├── VideoResolution.java │ │ ├── VideoInfo.java │ │ └── VideoFile.java │ │ ├── utils │ │ ├── EncriptUtils.java │ │ ├── FFmpegUtils.java │ │ └── FileUtils.java │ │ └── runner │ │ ├── FFmpegCommandRunner.java │ │ └── BaseCommandOption.java └── test │ ├── java │ └── com │ │ └── github │ │ └── tonydeng │ │ └── fmj │ │ ├── BaseTest.java │ │ ├── runner │ │ ├── BaseCommandOptionTest.java │ │ └── FFmpegCommandRunnerTest.java │ │ ├── utils │ │ ├── EncriptUtilsTest.java │ │ ├── FileUtilsTest.java │ │ └── FFmpegUtilsTest.java │ │ └── handler │ │ └── ProcessCallbackHandlerTest.java │ └── resources │ └── logback.xml ├── .travis.yml ├── README.md ├── pom.xml ├── .gitignore └── ffmpeg.md /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /fmj.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonydeng/fmj/HEAD/fmj.jpg -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/handler/ProcessCallbackHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.handler; 2 | 3 | import java.io.InputStream; 4 | 5 | /** 6 | * Created by tonydeng on 15/4/20. 7 | */ 8 | public interface ProcessCallbackHandler { 9 | String handler(InputStream inputStream) throws Exception; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/github/tonydeng/fmj/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * Created by tonydeng on 15/4/16. 9 | */ 10 | public class BaseTest { 11 | protected Logger log = LoggerFactory.getLogger(this.getClass()); 12 | 13 | @Test 14 | public void test(){ 15 | log.debug("base test......"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/github/tonydeng/fmj/runner/BaseCommandOptionTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.runner; 2 | 3 | import com.github.tonydeng.fmj.BaseTest; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by tonydeng on 15/4/20. 11 | */ 12 | @Ignore 13 | public class BaseCommandOptionTest extends BaseTest { 14 | @Test 15 | public void getFFprobeBinaryTest(){ 16 | List commands = BaseCommandOption.getFFprobeBinary(); 17 | 18 | log.info(commands.toString()); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/model/HLS.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.model; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by tonydeng on 15/4/17. 8 | */ 9 | public class HLS { 10 | private File m3u8; 11 | private List ts; 12 | 13 | public File getM3u8() { 14 | return m3u8; 15 | } 16 | 17 | public void setM3u8(File m3u8) { 18 | this.m3u8 = m3u8; 19 | } 20 | 21 | public List getTs() { 22 | return ts; 23 | } 24 | 25 | public void setTs(List ts) { 26 | this.ts = ts; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/github/tonydeng/fmj/utils/EncriptUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.utils; 2 | 3 | import com.github.tonydeng.fmj.BaseTest; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * @author Tony Deng 10 | * @version V1.0 11 | * @date 2019-09-10 00:28 12 | **/ 13 | @Slf4j 14 | public class EncriptUtilsTest extends BaseTest { 15 | 16 | private static final String INPUT = "12345"; 17 | 18 | @Test 19 | public void testMd5() { 20 | Assert.assertEquals("827CCB0EEA8A706C4C34A16891F84E7B",EncriptUtils.md5(INPUT)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/model/VideoResolution.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.model; 2 | 3 | /** 4 | * Created by tonydeng on 15/4/15. 5 | */ 6 | public class VideoResolution { 7 | 8 | private int width; 9 | private int height; 10 | 11 | public VideoResolution(int width, int height) { 12 | this.width = width; 13 | this.height = height; 14 | } 15 | 16 | public int getWidth() { 17 | return width; 18 | } 19 | 20 | public int getHeight() { 21 | return height; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "VideoResolution{" + 27 | "width=" + width + 28 | ", height=" + height + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/handler/DefaultCallbackHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.handler; 2 | 3 | import com.github.tonydeng.fmj.runner.BaseCommandOption; 4 | import com.google.common.collect.Lists; 5 | import lombok.Cleanup; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by tonydeng on 15/4/20. 16 | */ 17 | @Slf4j 18 | public class DefaultCallbackHandler implements ProcessCallbackHandler { 19 | 20 | @Override 21 | public String handler(InputStream inputStream) throws IOException { 22 | @Cleanup BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, BaseCommandOption.UTF8)); 23 | List buffer = Lists.newArrayList(); 24 | String line; 25 | while ((line = reader.readLine()) != null) { 26 | buffer.add(line); 27 | } 28 | return String.join("\n", buffer); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: java 4 | jdk: 5 | - oraclejdk8 6 | 7 | addons: 8 | sonarcloud: 9 | organization: "tonydeng-github" 10 | token: 11 | secure: "Q+CzDCcUcjvj/hK3owgsgG5YfK9+crXPQsOhoIjxeCNG6/7MMc02QLt671UAg7GaVo01qjIoBxse5WGRgxt3iHN+SbYHbjdw4gLsFlUaRoKg1bp9jW820gIi617M13rCDCF83e6r2AoE/nq1k/VZh4GrNqOWLAOLIqsltgqtB0HUo9XJOZ6OrLaDzDivDxkkQ6+NpYhxXxtyDFi9W2oTeOfi+ECAAlf94pt8rrHZy+BYCJF3oih9A80tIhphv0XiZ4fH5lcd+J/JI2ooCz30dQu5jTy/xJtvZCkBpLYxuYthF4vNNYaTD0RTba/b8FkBpurjrsOt4dYMNXeJvMwhhrQmKVhmsyBCHYOWJj+THCjhd1LY9tHRUYFWMWYCGkZwhiYNlDwWMwIIXaF16v9w8oTgQQRXTssOflKlzfX8HIwSSh6trffhMK1KZWoQS1wG/fviPyKabZn1r0XdvAmcXJmnMVK2de0pbiM8U0kwM8np7ekOpeOA6Z8SVVIa4dWsBIS6DxYYH6zXmOE8iWOncwFj2rffDVA1owz1Xv1EuKGQ9SizZHXXRhXZ0FzH1WYlMwk8dTQFSPahlBiVEts4JRvDpBdBn5QjHU4zEem4CZ6Phl3DXbVaxku8cdB1hDQ2UkXyPDVzofqerxjb7y0hXqeDRNuDuQmcBoOZAHHgKdI=" 12 | 13 | script: 14 | # JaCoCo is used to have code coverage, the agent has to be activated 15 | - mvn install -T20 16 | - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar 17 | 18 | cache: 19 | directories: 20 | - '$HOME/.m2/repository' 21 | - '$HOME/.sonar/cache' -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DEBUG 6 | 7 | 8 | 9 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/model/VideoInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.model; 2 | 3 | /** 4 | * Created by tonydeng on 15/4/16. 5 | */ 6 | public class VideoInfo { 7 | private String format; 8 | private long size; 9 | private long duration; 10 | private VideoResolution resolution; 11 | private int rotate; 12 | public VideoInfo(){} 13 | public VideoInfo(long size){ 14 | setSize(size); 15 | } 16 | public String getFormat() { 17 | return format; 18 | } 19 | 20 | public void setFormat(String format) { 21 | this.format = format; 22 | } 23 | 24 | public long getSize() { 25 | return size; 26 | } 27 | 28 | public void setSize(long size) { 29 | this.size = size; 30 | } 31 | 32 | public long getDuration() { 33 | return duration; 34 | } 35 | 36 | public void setDuration(long duration) { 37 | this.duration = duration; 38 | } 39 | 40 | public VideoResolution getResolution() { 41 | return resolution; 42 | } 43 | 44 | public void setResolution(VideoResolution resolution) { 45 | this.resolution = resolution; 46 | } 47 | 48 | public int getRotate() { 49 | return rotate; 50 | } 51 | 52 | public void setRotate(int rotate) { 53 | this.rotate = rotate; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "VideoInfo{" + 59 | "format='" + format + '\'' + 60 | ", size=" + size + 61 | ", duration=" + duration + 62 | ", resolution=" + resolution + 63 | ", rotate=" + rotate + 64 | '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/model/VideoFile.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.model; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Created by tonydeng on 15/4/22. 7 | */ 8 | public class VideoFile { 9 | private File input; 10 | private File target; 11 | private VideoInfo inputInfo; 12 | private VideoInfo targetInfo; 13 | private boolean isSuccess; 14 | public VideoFile(){} 15 | public VideoFile(File input,File target){ 16 | setInput(input); 17 | setTarget(target); 18 | } 19 | public File getInput() { 20 | return input; 21 | } 22 | 23 | public void setInput(File input) { 24 | this.input = input; 25 | } 26 | 27 | public File getTarget() { 28 | return target; 29 | } 30 | 31 | public void setTarget(File target) { 32 | this.target = target; 33 | } 34 | 35 | public VideoInfo getInputInfo() { 36 | return inputInfo; 37 | } 38 | 39 | public void setInputInfo(VideoInfo inputInfo) { 40 | this.inputInfo = inputInfo; 41 | } 42 | 43 | public VideoInfo getTargetInfo() { 44 | return targetInfo; 45 | } 46 | 47 | public void setTargetInfo(VideoInfo targetInfo) { 48 | this.targetInfo = targetInfo; 49 | } 50 | 51 | public boolean isSuccess() { 52 | return isSuccess; 53 | } 54 | 55 | public void setSuccess(boolean isSuccess) { 56 | this.isSuccess = isSuccess; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "VideoFile{" + 62 | "input=" + input.getAbsolutePath() + 63 | ", target=" + target.getAbsolutePath() + 64 | ", inputInfo=" + inputInfo + 65 | ", targetInfo=" + targetInfo + 66 | ", isSuccess=" + isSuccess + 67 | '}'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/utils/EncriptUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.security.MessageDigest; 6 | 7 | /** 8 | * Created by tonydeng on 15/4/17. 9 | */ 10 | @Slf4j 11 | public class EncriptUtils { 12 | //十六进制下数字到字符的映射数组 13 | private static final String[] HEX_DIGITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; 14 | 15 | private EncriptUtils() { 16 | 17 | } 18 | 19 | /** 20 | * 把inputString加密 21 | */ 22 | public static String md5(String inputStr) { 23 | return encodeByMD5(inputStr); 24 | } 25 | 26 | /** 27 | * 对字符串进行MD5编码 28 | */ 29 | private static String encodeByMD5(String originString) { 30 | if (originString != null) { 31 | try { 32 | //创建具有指定算法名称的信息摘要 33 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 34 | //使用指定的字节数组对摘要进行最后更新,然后完成摘要计算 35 | byte[] results = md5.digest(originString.getBytes()); 36 | //将得到的字节数组变成字符串返回 37 | return byteArrayToHexString(results); 38 | } catch (Exception e) { 39 | log.error("encode md5 error", e); 40 | } 41 | } 42 | return null; 43 | } 44 | 45 | /** 46 | * 轮换字节数组为十六进制字符串 47 | * 48 | * @param b 字节数组 49 | * @return 十六进制字符串 50 | */ 51 | private static String byteArrayToHexString(byte[] b) { 52 | StringBuilder builder = new StringBuilder(); 53 | for (int i = 0; i < b.length; i++) { 54 | builder.append(byteToHexString(b[i])); 55 | } 56 | return builder.toString(); 57 | } 58 | 59 | /** 60 | * 将一个字节转化成十六进制形式的字符串 61 | * 62 | * @param b 63 | * @return 64 | */ 65 | private static String byteToHexString(byte b) { 66 | int n = b; 67 | if (n < 0) 68 | n = 256 + n; 69 | int d1 = n / 16; 70 | int d2 = n % 16; 71 | return HEX_DIGITS[d1] + HEX_DIGITS[d2]; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目介绍 2 | 3 | [![Build Status](https://travis-ci.org/tonydeng/fmj.svg?branch=master)](https://travis-ci.org/tonydeng/fmj) 4 | [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=com.github.tonydeng:fmj&metric=alert_status)](https://sonarcloud.io/dashboard?id=com.github.tonydeng:fmj)
5 | [![SonarCloud Coverage](https://sonarcloud.io/api/project_badges/measure?project=com.github.tonydeng:fmj&metric=coverage)](https://sonarcloud.io/dashboard?id=com.github.tonydeng:fmj) 6 | [![Lines of code](https://sonarcloud.io/api/project_badges/measure?project=com.github.tonydeng:fmj&metric=ncloc)](https://sonarcloud.io/dashboard?id=com.github.tonydeng:fmj) 7 | [![SonarCloud Bugs](https://sonarcloud.io/api/project_badges/measure?project=com.github.tonydeng:fmj&metric=bugs)](https://sonarcloud.io/project/issues?id=com.github.tonydeng:fmj&resolved=false&types=BUG) 8 | [![SonarCloud vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=com.github.tonydeng:fmj&metric=vulnerabilities)](https://sonarcloud.io/component_measures/metric/security_rating/list?id=com.github.tonydeng:fmj) 9 | 10 | - [项目介绍](#%e9%a1%b9%e7%9b%ae%e4%bb%8b%e7%bb%8d) 11 | - [FMJ (FFMpeg for Java)](#fmj-ffmpeg-for-java) 12 | - [FFMpeg安装](#ffmpeg%e5%ae%89%e8%a3%85) 13 | - [Linux](#linux) 14 | - [Mac](#mac) 15 | - [Windows](#windows) 16 | - [具体的ffmpeg命令可以参考下面的文档](#%e5%85%b7%e4%bd%93%e7%9a%84ffmpeg%e5%91%bd%e4%bb%a4%e5%8f%af%e4%bb%a5%e5%8f%82%e8%80%83%e4%b8%8b%e9%9d%a2%e7%9a%84%e6%96%87%e6%a1%a3) 17 | - [其他解释](#%e5%85%b6%e4%bb%96%e8%a7%a3%e9%87%8a) 18 | - [全金属被甲弹(FMJ - Full Metal Jacket)](#%e5%85%a8%e9%87%91%e5%b1%9e%e8%a2%ab%e7%94%b2%e5%bc%b9fmj---full-metal-jacket) 19 | 20 | ## FMJ (FFMpeg for Java) 21 | 22 | ![FMJ Logo](fmj.jpg) 23 | 24 | 通过Java调用FFMpeg命令的方式来对音视频进行处理(获取信息、截图等等)。 25 | 26 | 27 | ## FFMpeg安装 28 | [FFMpeg官网](http://ffmpeg.org/) 29 | 30 | 建议使用 **ffmpeg-2.6.1** 版本 31 | 32 | ### Linux 33 | 34 | `yum install ffmpeg` 35 | 36 | `apt-get install ffmpeg` 37 | 38 | ### Mac 39 | 40 | `brew install ffmpeg` 41 | 42 | ### Windows 43 | 44 | 1. 可以在[这儿](http://ffmpeg.zeranoe.com/builds/)下载编译好的FFmpeg 45 | 2. 解压到 **/path/to/ffmpeg** 46 | 3. 添加 **/path/to/ffmpeg/bin** 到你的环境变量 **PATH** 中。 47 | 4. 打开命令行,执行 **ffmpeg -version** 48 | 49 | ## 具体的ffmpeg命令可以参考下面的文档 50 | 51 | [FFMpeg命令介绍](https://github.com/tonydeng/fmj/blob/master/ffmpeg.md) 52 | 53 | ## 其他解释 54 | 55 | ### 全金属被甲弹(FMJ - Full Metal Jacket) 56 | 57 | 弹头为铅质或铅锑合金以提升比重与质量,然而铅质延展性过强以致于如果直接作为弹头发射,会于击发时碎裂或与枪管摩擦产生变形,最后与大气作不良的空气动力结合而失去弹道精准性。因此将铅为铜所完全包覆,使弹头能够承受击发时的推进力又不会磨损变形;然而较轻的比重与质量使得全金属包覆弹进入密度高的目标物,例如人体(人体密度为大气的1000倍),就会因为因惯性而产生的动能扩散于目标物上,以至于动能对目标物所产生的作用力结合入射角/反射角的效应而产生滚转。这个滚转为预期与期盼的效果,尽管子弹终端弹道的滚转不可预期,然而滚转的途径势必能够造成深层广泛的肌肉撕裂伤,甚至切断动脉击碎骨骼,而造成人员严重的伤害与死亡。 58 | -------------------------------------------------------------------------------- /src/test/java/com/github/tonydeng/fmj/utils/FileUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.utils; 2 | 3 | import com.github.tonydeng.fmj.BaseTest; 4 | import com.google.common.collect.Lists; 5 | import org.junit.Ignore; 6 | import org.junit.Test; 7 | 8 | import java.io.File; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by tonydeng on 15/4/17. 13 | */ 14 | @Ignore 15 | public class FileUtilsTest extends BaseTest { 16 | private static final List files = Lists.newArrayList( 17 | new File("/Users/tonydeng/temp/m3u8/muse_va_v97.mp4"), 18 | new File("/Users/tonydeng/temp/m3u8/muse_va_v140.mp4"), 19 | new File("/Users/tonydeng/temp/m3u8/f/1"), 20 | new File("/Users/tonydeng/temp/m3u8/f/1.1"), 21 | new File("/Users/tonydeng/temp/m3u8/f/1.1."), 22 | new File("/Users/tonydeng/temp/m3u8/f/1.2"), 23 | new File("/Users/tonydeng/temp/m3u8/f/2"), 24 | new File("/Users/tonydeng/temp/m3u8/f/2."), 25 | new File("/Users/tonydeng/temp/m3u8/f/2.2"), 26 | new File("/Users/tonydeng/temp/m3u8/f/3"), 27 | new File("/Users/tonydeng/temp/m3u8/f/3.4.5"), 28 | new File("/Users/tonydeng/temp/m3u8/f/6.6.6.") 29 | ); 30 | 31 | // @Test 32 | public void createDirectoryTest() { 33 | List paths = Lists.newArrayList( 34 | "/Users/tonydeng/temp/1", 35 | "/Users/tonydeng/temp/1/1", 36 | "/Users/tonydeng/temp/1/2/2", 37 | "/opt/1" 38 | ); 39 | 40 | for (String path : paths) { 41 | log.info("create dir :'{}' status:'{}'", path, FileUtils.createDirectory(path)); 42 | } 43 | } 44 | 45 | // @Test 46 | public void getM3U8OutputByInputTest() { 47 | List inputs = Lists.newArrayList( 48 | new File("/Users/tonydeng/temp/m3u8/muse_va_v97.mp4"), 49 | new File("/Users/tonydeng/temp/m3u8/muse_va_v140.mp4") 50 | ); 51 | 52 | for (File input : inputs) { 53 | log.info("input:'{}' meu8:'{}'", input.getAbsolutePath(), FileUtils.getM3U8OutputByInput(input)); 54 | } 55 | } 56 | 57 | // @Test 58 | public void findTSTest() { 59 | File m3u8 = new File("/Users/tonydeng/temp/m3u8/v100/m3u8/muse_va_v100.m3u8"); 60 | List ts = FileUtils.findTS(m3u8); 61 | for (File t : ts) { 62 | log.info(t.getAbsolutePath()); 63 | } 64 | } 65 | 66 | // @Test 67 | public void getFileNameAndExtendTest() { 68 | for (File file : files) { 69 | log.info("file:'{}' name:'{}' extend:'{}'", file.getAbsolutePath(), FileUtils.getFileName(file), FileUtils.getFileExtend(file)); 70 | } 71 | } 72 | 73 | @Test 74 | public void getMp4OutputByInputTest() { 75 | for (File input : files) { 76 | log.info("file:'{}' mp4 file:'{}'", input.getAbsolutePath(), FileUtils.getMp4OutputByInput(input)); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/github/tonydeng/fmj/utils/FFmpegUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.utils; 2 | 3 | import com.github.tonydeng.fmj.BaseTest; 4 | import com.github.tonydeng.fmj.model.VideoInfo; 5 | import com.google.common.collect.Lists; 6 | import org.junit.Test; 7 | 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Created by tonydeng on 15/4/17. 14 | */ 15 | //@Ignore 16 | public class FFmpegUtilsTest extends BaseTest { 17 | private static final String stdout = "ffprobe version 2.6.1 Copyright (c) 2007-2015 the FFmpeg developers\n" + 18 | " built with Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)\n" + 19 | " configuration: --prefix=/usr/local/Cellar/ffmpeg/2.6.1 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-libx264 --enable-libmp3lame --enable-libvo-aacenc --enable-libxvid --enable-vda\n" + 20 | " libavutil 54. 20.100 / 54. 20.100\n" + 21 | " libavcodec 56. 26.100 / 56. 26.100\n" + 22 | " libavformat 56. 25.101 / 56. 25.101\n" + 23 | " libavdevice 56. 4.100 / 56. 4.100\n" + 24 | " libavfilter 5. 11.102 / 5. 11.102\n" + 25 | " libavresample 2. 1. 0 / 2. 1. 0\n" + 26 | " libswscale 3. 1.101 / 3. 1.101\n" + 27 | " libswresample 1. 1.100 / 1. 1.100\n" + 28 | " libpostproc 53. 3.100 / 53. 3.100\n" + 29 | "Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'VID_20150414_191241.mp4':\n" + 30 | " Metadata:\n" + 31 | " major_brand : isom\n" + 32 | " minor_version : 0\n" + 33 | " compatible_brands: isom3gp4\n" + 34 | " creation_time : 2015-04-14 11:12:45\n" + 35 | " location : +39.9947+116.4754/\n" + 36 | " location-eng : +39.9947+116.4754/\n" + 37 | " Duration: 00:00:03.58, start: 0.000000, bitrate: 9178 kb/s\n" + 38 | " Stream #0:0(eng): Video: h264 (Baseline) (avc1 / 0x31637661), yuv420p, 1280x720, 7842 kb/s, SAR 1:1 DAR 16:9, 29.18 fps, 29.25 tbr, 90k tbn, 180k tbc (default)\n" + 39 | " Metadata:\n" + 40 | " rotate : 90\n" + 41 | " creation_time : 2015-04-14 11:12:45\n" + 42 | " handler_name : VideoHandle\n" + 43 | " Side data:\n" + 44 | " displaymatrix: rotation of -90.00 degrees\n" + 45 | " Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 97 kb/s (default)\n" + 46 | " Metadata:\n" + 47 | " creation_time : 2015-04-14 11:12:45\n" + 48 | " handler_name : SoundHandle"; 49 | // @Test 50 | public void regInfoTest(){ 51 | VideoInfo vi = FFmpegUtils.regInfo(stdout); 52 | log.debug(vi.toString()); 53 | } 54 | 55 | // @Test 56 | public void getRotateTest(){ 57 | String str = " rotate : 90\n"; 58 | String regexRotate = "rotate (.*?): (\\d*)(\\d*)"; 59 | Pattern patternRotate = Pattern.compile(regexRotate); 60 | Matcher matcherRotate = patternRotate.matcher(str); 61 | if (matcherRotate.find()) { 62 | for (int i = 0; i < matcherRotate.groupCount(); i++) { 63 | if (log.isDebugEnabled()) { 64 | log.debug("index {} : rotate:'{}'", i, matcherRotate.group(i)); 65 | } 66 | } 67 | // String rotate = matcherRotate.group(0); 68 | // if (rotate.indexOf(":") > 0) { 69 | // vi.setRotate(Integer.valueOf(rotate.substring(rotate.indexOf(":") + 1, rotate.length()).trim())); 70 | // } 71 | } 72 | } 73 | @Test 74 | public void ffmpegCmdLineTest(){ 75 | List commands = Lists.newArrayList( 76 | "test" , 77 | "test2", 78 | "test3", 79 | "test4" 80 | ); 81 | 82 | log.info(FFmpegUtils.ffmpegCmdLine(commands)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/com/github/tonydeng/fmj/runner/FFmpegCommandRunnerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.runner; 2 | 3 | import com.github.tonydeng.fmj.BaseTest; 4 | import com.github.tonydeng.fmj.model.HLS; 5 | import com.github.tonydeng.fmj.model.VideoFile; 6 | import com.github.tonydeng.fmj.model.VideoInfo; 7 | import com.github.tonydeng.fmj.utils.FileUtils; 8 | import com.google.common.collect.Lists; 9 | import org.junit.Before; 10 | import org.junit.Ignore; 11 | import org.junit.Test; 12 | 13 | import java.io.File; 14 | import java.util.List; 15 | 16 | /** 17 | * Created by tonydeng on 15/4/16. 18 | */ 19 | @Ignore 20 | public class FFmpegCommandRunnerTest extends BaseTest { 21 | private static final List inputs_win = Lists.newArrayList( 22 | new File("D:\\temp\\test2.mp4"), 23 | new File("D:\\temp\\test.flv") 24 | ); 25 | private static final List inputs_linux = Lists.newArrayList( 26 | new File("/home/chichen/test/test.flv"), 27 | new File("/home/chichen/test//test2.mp4") 28 | ); 29 | private static final List inputs_mac = Lists.newArrayList( 30 | // new File("/Users/tonydeng/temp/m3u8/2013.flv"), 31 | // 32 | new File("/users/tonydeng/temp/m3u8/2013.flv"), 33 | new File("/users/tonydeng/temp/m3u8/IMG_1666.MOV") 34 | // new File("/Users/tonydeng/temp/m3u8/IMG_1666.MOV") 35 | 36 | ); 37 | private static List inputs; 38 | 39 | @Before 40 | public void init() { 41 | String env = System.getProperty("os.name"); 42 | if (null != env) { 43 | String os = env.toLowerCase(); 44 | if (os.indexOf("win") >= 0) { 45 | log.info("****************** is WIN OS"); 46 | inputs = inputs_win; 47 | } else if (os.indexOf("linux") >= 0) { 48 | log.info("****************** is LINUX OS"); 49 | inputs = inputs_linux; 50 | } else if (os.indexOf("mac") >= 0) { 51 | log.info("****************** is MAC OS"); 52 | inputs = inputs_mac; 53 | } 54 | } 55 | } 56 | 57 | // @Test 58 | public void getVideoInfoTest() { 59 | 60 | for (File input : inputs) { 61 | VideoInfo vi = FFmpegCommandRunner.getVideoInfo(input); 62 | log.info("{} video info: {}", input.getAbsoluteFile(), vi.toString()); 63 | } 64 | } 65 | 66 | // @Test 67 | public void screenshotTest() { 68 | for (File input : inputs) { 69 | File output = FFmpegCommandRunner.screenshot(input, 70 | 10); 71 | log.info("input : {}, output:{}", input.getAbsoluteFile(), output.getAbsoluteFile()); 72 | } 73 | } 74 | 75 | @Test 76 | public void generationHlsTest() { 77 | for (File input : inputs) { 78 | HLS hls = FFmpegCommandRunner.generationHls(input, 3, "http://dl.duoquyuedu.com/m3u8/"+ FileUtils.getFileName(input)+"/"); 79 | if (hls != null) { 80 | log.info("m3u8 path:'{}'", hls.getM3u8().getAbsolutePath()); 81 | for (File ts : hls.getTs()) { 82 | log.info("ts path:'{}'", ts.getAbsolutePath()); 83 | } 84 | } 85 | } 86 | } 87 | 88 | // @Test 89 | public void coverToMp4Test() { 90 | for (File input : inputs) { 91 | VideoFile vf = FFmpegCommandRunner.coverToMp4(input); 92 | if (vf.isSuccess()) 93 | log.info(vf.toString()); 94 | } 95 | } 96 | // @Test 97 | public void parallelCoverMp4Test(){ 98 | for(File input:inputs){ 99 | VideoInfo vi = FFmpegCommandRunner.getVideoInfo(input); 100 | List commands = Lists.newArrayList( 101 | "parallel", 102 | "--will-cite", 103 | "-j","64", 104 | "ffmpeg", 105 | "-i","{}", 106 | "-vf","transpose=1", 107 | "-c:v","ibx264", 108 | "-c:v","libx264","-c:a","aac", 109 | " -strict","-2","-threads","8", 110 | "{.}_paraller.mp4", 111 | ":::",input.getAbsolutePath() 112 | ); 113 | 114 | // log.info(FFmpegUtils.ffmpegCmdLine(commands)); 115 | FFmpegCommandRunner.runProcess(commands); 116 | } 117 | 118 | 119 | 120 | 121 | // commands.addAll(BaseCommandOption.toMP4CmdArrays(,"/Users/tonydeng/temp/m3u8/MG_1666.mp4")); 122 | 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/utils/FFmpegUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.utils; 2 | 3 | import com.github.tonydeng.fmj.model.VideoInfo; 4 | import com.github.tonydeng.fmj.model.VideoResolution; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Created by tonydeng on 15/4/15. 14 | */ 15 | @Slf4j 16 | public class FFmpegUtils { 17 | private static final String REGEX_DURATION = "Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s"; 18 | private static final String REGEX_VIDEO = "Video: (.*?), (.*?), (.*?)[,\\s]"; 19 | private static final String REGEX_AUDIO = "Audio: (\\w*), (\\d*) Hz"; 20 | private static final String REGEX_ROTATE = "rotate (.*?): (\\d*)(\\d*)"; 21 | 22 | private FFmpegUtils() { 23 | } 24 | 25 | public static VideoInfo regInfo(String stdout) { 26 | return regInfo(stdout, null); 27 | } 28 | 29 | /** 30 | * @param stdout 31 | * @return 32 | */ 33 | public static VideoInfo regInfo(String stdout, VideoInfo vi) { 34 | if (StringUtils.isNotEmpty(stdout)) { 35 | if (vi == null) { 36 | vi = new VideoInfo(); 37 | } 38 | 39 | Pattern patternDuration = Pattern.compile(REGEX_DURATION); 40 | Matcher matcherDuration = patternDuration.matcher(stdout); 41 | if (matcherDuration.find()) { 42 | String duration = matcherDuration.group(1); 43 | if (StringUtils.isNotBlank(duration) && duration.indexOf(':') >= 0) { 44 | String[] time = duration.split(":"); 45 | int hour = Integer.parseInt(time[0]); 46 | int minute = Integer.parseInt(time[1]); 47 | int second = 0; 48 | if (time[2].indexOf('.') >= 0) { 49 | second = Integer.valueOf(time[2].substring(0, time[2].indexOf('.'))); 50 | } else { 51 | second = Integer.valueOf(time[2]); 52 | } 53 | vi.setDuration((long) ((hour * 60 * 60) + (minute * 60) + second)); 54 | } 55 | 56 | // map.put("提取出播放时间", matcherDuration.group(1)); 57 | // map.put("开始时间", matcherDuration.group(2)); 58 | // map.put("bitrate 码率 单位 kb", matcherDuration.group(3)); 59 | } 60 | 61 | Pattern patternVideo = Pattern.compile(REGEX_VIDEO); 62 | Matcher matcherVideo = patternVideo.matcher(stdout); 63 | 64 | if (matcherVideo.find()) { 65 | // map.put("编码格式", matcherVideo.group(1)); 66 | // map.put("视频格式", matcherVideo.group(2)); 67 | // map.put("分辨率", matcherVideo.group(3)); 68 | String[] wh = matcherVideo.group(3).split("x"); 69 | if (null != wh && wh.length == 2) { 70 | vi.setResolution(new VideoResolution(Integer.valueOf(wh[0]), Integer.valueOf(wh[1]))); 71 | } 72 | String format = matcherVideo.group(1); 73 | if (StringUtils.isNotBlank(format)) { 74 | vi.setFormat(format.split(" ")[0]); 75 | } 76 | 77 | } 78 | // Pattern patternAudio = Pattern.compile(REGEX_AUDIO); 79 | // Matcher matcherAudio = patternAudio.matcher(info); 80 | // 81 | // if (matcherAudio.find()) { 82 | //// map.put("音频编码", matcherAudio.group(1)); 83 | //// map.put("音频采样频率", matcherAudio.group(2)); 84 | // ai.setDecoder(matcherAudio.group(1)); 85 | // } 86 | 87 | Pattern patternRotate = Pattern.compile(REGEX_ROTATE); 88 | Matcher matcherRotate = patternRotate.matcher(stdout); 89 | if (matcherRotate.find()) { 90 | // for (int i = 0; i < matcherRotate.groupCount(); i++) { 91 | // if (log.isDebugEnabled()) { 92 | // log.debug("index {} : rotate:'{}'", i, matcherRotate.group(i)); 93 | // } 94 | // } 95 | String rotate = matcherRotate.group(2); 96 | if (StringUtils.isNumeric(rotate)) { 97 | vi.setRotate(Integer.valueOf(rotate)); 98 | } 99 | } 100 | return vi; 101 | } 102 | return null; 103 | } 104 | 105 | 106 | /** 107 | * 构建ffmpeg命令 108 | * 109 | * @param commands 110 | * @return 111 | */ 112 | public static String ffmpegCmdLine(List commands) { 113 | return String.join(" ",commands); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.utils; 2 | 3 | import com.google.common.collect.Lists; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.FilenameFilter; 10 | import java.io.IOException; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by tonydeng on 15/4/17. 16 | */ 17 | @Slf4j 18 | public class FileUtils { 19 | private static final String PATH_SRCEENTSHOT = "srceent"; 20 | private static final String PATH_HLS = "hls"; 21 | private static final String EXTEND_JPG = ".jpg"; 22 | private static final String EXTEND_M3U8 = ".m3u8"; 23 | private static final String EXTEND_TS = ".ts"; 24 | private static final String EXTEND_MP4 = ".mp4"; 25 | 26 | private FileUtils() { 27 | } 28 | 29 | /** 30 | * 获得文件大小 31 | * 32 | * @param input 33 | * @return 34 | */ 35 | public static long getFineSize(File input) { 36 | if (input != null && input.exists()) { 37 | try { 38 | return new FileInputStream(input).available(); 39 | } catch (IOException e) { 40 | log.error("getFineSize error:'{}'", e.getMessage()); 41 | } 42 | } 43 | return 0; 44 | } 45 | 46 | /** 47 | * 根据视频文件获得视频截图文件 48 | * 49 | * @param input 50 | * @return 51 | */ 52 | public static File getSrceentshotOutputByInput(File input) { 53 | if (input != null && input.exists()) { 54 | File outputPath = new File(input.getParent() + File.separator + PATH_SRCEENTSHOT); 55 | if (createDirectory(outputPath)) { 56 | return new File( 57 | outputPath.getAbsolutePath() 58 | + File.separator 59 | + EncriptUtils.md5(input.getAbsolutePath()).substring(0, 6).toLowerCase() 60 | + EXTEND_JPG 61 | ); 62 | } 63 | } 64 | return null; 65 | } 66 | 67 | /** 68 | * 根据视频文件获得HLS的m3u8文件 69 | * 70 | * @param input 71 | * @return 72 | */ 73 | public static File getM3U8OutputByInput(File input) { 74 | String videoName = getFileName(input); 75 | if (StringUtils.isNotEmpty(videoName)) { 76 | File outputPath = new File(input.getParent() + File.separator + PATH_HLS + File.separator + videoName); 77 | if (createDirectory(outputPath)) { 78 | return new File(outputPath.getAbsolutePath() 79 | + File.separator 80 | + videoName 81 | + EXTEND_M3U8); 82 | } 83 | } 84 | return null; 85 | } 86 | 87 | /** 88 | * 通过m3u8文件获得该目录下的所有ts文件 89 | * 90 | * @param m3u8 91 | * @return 92 | */ 93 | public static List findTS(File m3u8) { 94 | if (m3u8 != null && m3u8.exists()) { 95 | final File path = m3u8.getParentFile(); 96 | if (path.isDirectory()) { 97 | FilenameFilter filter = (dir, name) -> name.endsWith(EXTEND_TS); 98 | return Lists.newArrayList(path.listFiles(filter)); 99 | } 100 | } 101 | return Collections.emptyList(); 102 | } 103 | 104 | /** 105 | * 根据视频文件获得生成的mp4文件地址 106 | * 107 | * @param input 108 | * @return 109 | */ 110 | public static File getMp4OutputByInput(File input) { 111 | String videoName = getFileName(input); 112 | if (StringUtils.isNotEmpty(videoName) && !"mp4".equals(getFileExtend(input))) { 113 | return new File(input.getParent() + File.separator + videoName + EXTEND_MP4); 114 | } 115 | return null; 116 | } 117 | 118 | /** 119 | * 创建目录 120 | * 121 | * @param path 122 | * @return 123 | */ 124 | public static boolean createDirectory(String path) { 125 | if (StringUtils.isNotEmpty(path)) { 126 | return createDirectory(new File(path)); 127 | } 128 | return false; 129 | } 130 | 131 | /** 132 | * 创建目录 133 | * 134 | * @param path 135 | * @return 136 | */ 137 | public static boolean createDirectory(File path) { 138 | if (path.exists()) { 139 | return path.isDirectory(); 140 | } else { 141 | return path.mkdirs(); 142 | } 143 | } 144 | 145 | /** 146 | * 获得文件名 147 | * 148 | * @param file 149 | * @return 150 | */ 151 | public static String getFileName(File file) { 152 | if (file != null && file.exists() && file.isFile()) { 153 | if (file.getName().lastIndexOf('.') > 0) { 154 | return file.getName().substring(0, file.getName().lastIndexOf('.')).toLowerCase(); 155 | } else { 156 | return file.getName(); 157 | } 158 | } 159 | return null; 160 | } 161 | 162 | /** 163 | * 获得文件扩展名 164 | * 165 | * @param file 166 | * @return 167 | */ 168 | public static String getFileExtend(File file) { 169 | if (file != null && file.exists() 170 | && file.isFile() 171 | && file.getName().lastIndexOf('.') > 0) { 172 | return file.getName().substring(file.getName().lastIndexOf('.') + 1, file.getName().length()).toLowerCase(); 173 | } 174 | return null; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/com/github/tonydeng/fmj/handler/ProcessCallbackHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.handler; 2 | 3 | import com.github.tonydeng.fmj.BaseTest; 4 | import com.google.common.base.Charsets; 5 | import com.google.common.base.Stopwatch; 6 | import com.google.common.collect.Lists; 7 | import com.google.common.io.ByteStreams; 8 | import com.google.common.io.CharStreams; 9 | import org.apache.commons.io.IOUtils; 10 | import org.junit.Before; 11 | import org.junit.Ignore; 12 | import org.junit.Test; 13 | 14 | import java.io.*; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.List; 17 | import java.util.Scanner; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * Created by tonydeng on 15/5/12. 22 | */ 23 | @Ignore 24 | public class ProcessCallbackHandlerTest extends BaseTest { 25 | private static InputStream input; 26 | private static long fileSize; 27 | private List numbers = Lists.newArrayList( 28 | 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 29 | ); 30 | 31 | @Before 32 | public void init() throws FileNotFoundException { 33 | File f = new File("/Users/tonydeng/temp/api-top-book-name.log"); 34 | input = new FileInputStream(f); 35 | fileSize = f.length(); 36 | } 37 | 38 | @Test 39 | public void scannerHandlerTest() throws Exception { 40 | for (int number : numbers) { 41 | Stopwatch stopwatch = Stopwatch.createStarted(); 42 | for (int i = 0; i < number; i++) { 43 | Scanner scanner = new Scanner(input); 44 | StringBuffer sb = new StringBuffer(); 45 | while (scanner.hasNextLine()) { 46 | sb.append(scanner.nextLine()).append("\n"); 47 | } 48 | // String result = handler.handler(input); 49 | // log.info("scanner handler result:'{}'", result); 50 | } 51 | stopwatch.stop(); 52 | if (log.isInfoEnabled()) { 53 | log.info("scannerHandlerTest file size '{}' run number : '{}' use time {} seconds, {} milliseconds", 54 | fileSize, number, 55 | stopwatch.elapsed(TimeUnit.SECONDS), 56 | stopwatch.elapsed(TimeUnit.MILLISECONDS)); 57 | } 58 | } 59 | 60 | } 61 | 62 | 63 | @Test 64 | public void defaultHandlerTest() throws Exception { 65 | DefaultCallbackHandler handler = new DefaultCallbackHandler(); 66 | for (int number : numbers) { 67 | Stopwatch stopwatch = Stopwatch.createStarted(); 68 | for (int i = 0; i < number; i++) { 69 | handler.handler(input); 70 | // String result = handler.handler(input); 71 | // log.info("default handler result:'{}'", result); 72 | } 73 | stopwatch.stop(); 74 | if (log.isInfoEnabled()) { 75 | log.info("defaultHandlerTest file size '{}' run number : '{}' use time {} seconds, {} milliseconds", 76 | fileSize, number, 77 | stopwatch.elapsed(TimeUnit.SECONDS), 78 | stopwatch.elapsed(TimeUnit.MILLISECONDS)); 79 | } 80 | } 81 | } 82 | 83 | @Test 84 | public void byteStreamsTest() throws IOException { 85 | for (int number : numbers) { 86 | Stopwatch stopwatch = Stopwatch.createStarted(); 87 | for (int i = 0; i < number; i++) { 88 | new String(ByteStreams.toByteArray(input), Charsets.UTF_8); 89 | // log.info("byteStreamsTest result:'{}'", result); 90 | } 91 | stopwatch.stop(); 92 | if (log.isInfoEnabled()) { 93 | log.info("byteStreamsTest file size '{}' run number : '{}' use time {} seconds, {} milliseconds", 94 | fileSize, number, 95 | stopwatch.elapsed(TimeUnit.SECONDS), 96 | stopwatch.elapsed(TimeUnit.MILLISECONDS)); 97 | } 98 | } 99 | } 100 | 101 | @Test 102 | public void charStreamsTest() throws IOException { 103 | for (int number : numbers) { 104 | Stopwatch stopwatch = Stopwatch.createStarted(); 105 | for (int i = 0; i < number; i++) { 106 | new String(CharStreams.toString(new InputStreamReader(input))); 107 | // log.info("charStreamsTest result:'{}'", result); 108 | } 109 | stopwatch.stop(); 110 | if (log.isInfoEnabled()) { 111 | log.info("charStreamsTest file size '{}' run number : '{}' use time {} seconds, {} milliseconds", 112 | fileSize, number, 113 | stopwatch.elapsed(TimeUnit.SECONDS), 114 | stopwatch.elapsed(TimeUnit.MILLISECONDS)); 115 | } 116 | } 117 | } 118 | 119 | @Test 120 | public void ioUtilsTest() throws IOException { 121 | for (int number : numbers) { 122 | Stopwatch stopwatch = Stopwatch.createStarted(); 123 | for (int i = 0; i < number; i++) { 124 | IOUtils.toString(input, StandardCharsets.UTF_8); 125 | // log.info("ioUtilsTest result:'{}'", result); 126 | } 127 | stopwatch.stop(); 128 | if (log.isInfoEnabled()) { 129 | log.info("ioUtilsTest file size '{}' run number : '{}' use time {} seconds, {} milliseconds", 130 | fileSize, number, 131 | stopwatch.elapsed(TimeUnit.SECONDS), 132 | stopwatch.elapsed(TimeUnit.MILLISECONDS)); 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.github.tonydeng 5 | fmj 6 | 1.2-SNAPSHOT 7 | jar 8 | 9 | fmj 10 | http://maven.apache.org 11 | 12 | scm:git:git@github.com:tonydeng/fmj.git 13 | scm:git:git@github.com:tonydeng/fmj.git 14 | HEAD 15 | 16 | 17 | UTF-8 18 | 4.13.1 19 | 4.1.4.RELEASE 20 | 1.2.0 21 | 3.9 22 | 2.7 23 | [30.0-jre,) 24 | 1.18.8 25 | 26 | 27 | 28 | 29 | junit 30 | junit 31 | ${junit.version} 32 | test 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | ${lombok.version} 38 | 39 | 40 | org.springframework 41 | spring-test 42 | ${spring.version} 43 | test 44 | 45 | 46 | ch.qos.logback 47 | logback-classic 48 | ${logback.version} 49 | provided 50 | 51 | 52 | org.apache.commons 53 | commons-lang3 54 | ${commons-lang3.version} 55 | provided 56 | 57 | 58 | com.google.guava 59 | guava 60 | ${guava.version} 61 | provided 62 | 63 | 64 | commons-io 65 | commons-io 66 | ${commons-io.version} 67 | test 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 3.1 76 | 77 | 1.8 78 | 1.8 79 | utf-8 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-resources-plugin 86 | 2.6 87 | 88 | UTF-8 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-javadoc-plugin 94 | 2.9.1 95 | 96 | UTF-8 97 | UTF-8 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-deploy-plugin 103 | 2.8.1 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-enforcer-plugin 108 | 1.3.1 109 | 110 | 111 | enforce-versions 112 | 113 | enforce 114 | 115 | 116 | 117 | 118 | 3.1 119 | 120 | 121 | 1.8 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | org.apache.maven.wagon 132 | wagon-ssh 133 | 2.5 134 | 135 | 136 | 137 | 138 | 139 | oschina nexus 140 | http://maven.oschina.net/content/groups/public/ 141 | 142 | false 143 | 144 | 145 | 146 | oschina thirdparty 147 | http://maven.oschina.net/content/repositories/thirdparty/ 148 | 149 | false 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/runner/FFmpegCommandRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.runner; 2 | 3 | import com.github.tonydeng.fmj.handler.DefaultCallbackHandler; 4 | import com.github.tonydeng.fmj.handler.ProcessCallbackHandler; 5 | import com.github.tonydeng.fmj.model.HLS; 6 | import com.github.tonydeng.fmj.model.VideoFile; 7 | import com.github.tonydeng.fmj.model.VideoInfo; 8 | import com.github.tonydeng.fmj.utils.FFmpegUtils; 9 | import com.github.tonydeng.fmj.utils.FileUtils; 10 | import com.google.common.base.Stopwatch; 11 | import com.google.common.collect.Lists; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.util.List; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * Created by tonydeng on 15/4/16. 23 | */ 24 | public class FFmpegCommandRunner { 25 | private static final Logger log = LoggerFactory.getLogger(FFmpegCommandRunner.class); 26 | 27 | /** 28 | * 获取视频信息 29 | * 30 | * @param input 31 | * @return 32 | */ 33 | public static VideoInfo getVideoInfo(File input) { 34 | VideoInfo vi = new VideoInfo(); 35 | if (input != null && input.exists()) { 36 | List commands = Lists.newArrayList(BaseCommandOption.getFFprobeBinary()); 37 | commands.add(input.getAbsolutePath()); 38 | vi.setSize(FileUtils.getFineSize(input)); 39 | if (vi.getSize() > 0) { 40 | return FFmpegUtils.regInfo(runProcess(commands), vi); 41 | } 42 | } else { 43 | if (log.isErrorEnabled()) 44 | log.error("video '{}' is not fount! ", input.getAbsolutePath()); 45 | } 46 | 47 | return vi; 48 | } 49 | 50 | /** 51 | * 视频截图 52 | * 53 | * @param input 54 | * @param shotSecond 55 | * @return 56 | */ 57 | public static File screenshot(File input, int shotSecond) { 58 | File output = FileUtils.getSrceentshotOutputByInput(input); 59 | if (output != null) { 60 | VideoInfo vi = getVideoInfo(input); 61 | List commands = Lists.newArrayList(BaseCommandOption.getFFmpegBinary()); 62 | commands.addAll(BaseCommandOption.toScreenshotCmdArrays(input.getAbsolutePath(), output.getAbsolutePath(), shotSecond, vi)); 63 | if (StringUtils.isNotEmpty(runProcess(commands))) { 64 | return output; 65 | } 66 | } else { 67 | if (log.isErrorEnabled()) 68 | log.error("video '{}' screentshot '{}' create error!", input.getAbsolutePath(), output.getAbsolutePath()); 69 | } 70 | return null; 71 | } 72 | 73 | /** 74 | * 生成HLS 75 | * 76 | * @param input 77 | * @param cutSecond 78 | * @param tsBaseUrl 79 | * @return 80 | */ 81 | public static HLS generationHls(File input, int cutSecond, String tsBaseUrl) { 82 | File output = FileUtils.getM3U8OutputByInput(input); 83 | if (output != null) { 84 | VideoInfo vi = getVideoInfo(input); 85 | List commands = Lists.newArrayList(BaseCommandOption.getFFmpegBinary()); 86 | commands.addAll(BaseCommandOption.toHLSCmdArrays(input.getAbsolutePath(), output.getAbsolutePath(), cutSecond, tsBaseUrl, vi)); 87 | if (StringUtils.isNotEmpty(runProcess(commands))) { 88 | HLS hls = new HLS(); 89 | hls.setM3u8(output); 90 | hls.setTs(FileUtils.findTS(output)); 91 | return hls; 92 | } 93 | 94 | } else { 95 | if (log.isErrorEnabled()) 96 | log.error("vidoe '{}' m3u8 '{}' create error!", input.getAbsolutePath(), output.getAbsolutePath()); 97 | } 98 | 99 | return null; 100 | } 101 | 102 | /** 103 | * 转换视频格式为MP4 104 | * 105 | * @param input 106 | * @return 107 | */ 108 | public static VideoFile coverToMp4(File input) { 109 | VideoFile vf = new VideoFile(input, FileUtils.getMp4OutputByInput(input)); 110 | if (vf.getTarget() != null && !vf.getTarget().exists()) { 111 | vf.setInputInfo(getVideoInfo(input)); 112 | if (vf.getInputInfo().getSize() > 0 113 | /** && !BaseCommandOption.H264.equals(vf.getInputInfo().getFormat()) **/) { 114 | List commands = Lists.newArrayList(BaseCommandOption.getFFmpegBinary()); 115 | 116 | commands.addAll(BaseCommandOption.toMP4CmdArrays( 117 | input.getAbsolutePath(), 118 | vf.getTarget().getAbsolutePath(), 119 | vf.getInputInfo() 120 | )); 121 | 122 | if (StringUtils.isNotEmpty(runProcess(commands))) { 123 | vf.setTargetInfo(getVideoInfo(vf.getTarget())); 124 | vf.setSuccess(true); 125 | return vf; 126 | } 127 | } 128 | 129 | } 130 | return vf; 131 | } 132 | 133 | /** 134 | * 执行命令 135 | * 136 | * @param commands 137 | * @return 138 | */ 139 | public static String runProcess(List commands) { 140 | try { 141 | return runProcess(commands, null); 142 | } catch (Exception e) { 143 | e.printStackTrace(); 144 | } 145 | return null; 146 | } 147 | 148 | /** 149 | * 执行命令 150 | * 151 | * @param commands 152 | * @param handler 153 | * @return 154 | * @throws Exception 155 | */ 156 | public static String runProcess(List commands, ProcessCallbackHandler handler) { 157 | ProcessBuilder pb = null; 158 | Process process = null; 159 | 160 | if (log.isDebugEnabled()) 161 | log.debug("start to run ffmpeg process... cmd : '{}'", FFmpegUtils.ffmpegCmdLine(commands)); 162 | Stopwatch stopwatch = Stopwatch.createStarted(); 163 | pb = new ProcessBuilder(commands); 164 | 165 | pb.redirectErrorStream(true); 166 | 167 | if (null == handler) { 168 | handler = new DefaultCallbackHandler(); 169 | } 170 | 171 | String result = null; 172 | try { 173 | process = pb.start(); 174 | result = handler.handler(process.getInputStream()); 175 | } catch (Exception e) { 176 | log.error("errorStream:{}", result, e); 177 | } finally { 178 | if (null != process) { 179 | try { 180 | process.getInputStream().close(); 181 | process.getOutputStream().close(); 182 | process.getErrorStream().close(); 183 | } catch (IOException e) { 184 | e.printStackTrace(); 185 | } 186 | } 187 | } 188 | 189 | try { 190 | int flag = process.waitFor(); 191 | if (flag != 0) { 192 | throw new IllegalThreadStateException("process exit with error value : " + flag); 193 | } 194 | } catch (InterruptedException e) { 195 | log.error("wait for process finish error:{}", e); 196 | } finally { 197 | if (null != process) { 198 | process.destroy(); 199 | pb = null; 200 | } 201 | 202 | stopwatch.stop(); 203 | } 204 | if (log.isInfoEnabled()) { 205 | log.info("ffmpeg run {} seconds, {} milliseconds", 206 | stopwatch.elapsed(TimeUnit.SECONDS), 207 | stopwatch.elapsed(TimeUnit.MILLISECONDS)); 208 | } 209 | return result; 210 | } 211 | } -------------------------------------------------------------------------------- /src/main/java/com/github/tonydeng/fmj/runner/BaseCommandOption.java: -------------------------------------------------------------------------------- 1 | package com.github.tonydeng.fmj.runner; 2 | 3 | import com.github.tonydeng.fmj.model.VideoInfo; 4 | import com.google.common.collect.Lists; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by tonydeng on 15/4/15. 13 | */ 14 | @Slf4j 15 | public class BaseCommandOption { 16 | private static boolean isWin = false; 17 | private static boolean isLinux = false; 18 | private static Integer defaultIoThreads = 1; 19 | 20 | private static List ffmpegBinary; 21 | private static List ffprobeBinary; 22 | 23 | public static final String WINCMD = "cmd"; 24 | public static final String WINCMDOP = "/c"; 25 | public static final String LINUXCMD = "/usr/bin/env"; 26 | public static final String FFMPEG = "ffmpeg"; 27 | public static final String FFPROBE = "ffprobe"; 28 | 29 | public static final String Y = "-y"; 30 | public static final String INPUT = "-i"; 31 | public static final String T = "-t"; 32 | public static final String F = "-f"; 33 | public static final String S = "-s"; 34 | public static final String SS = "-ss"; 35 | public static final String CV = "-c:v"; 36 | public static final String CA = "-c:a"; 37 | public static final String STRICT = "-strict"; 38 | public static final String VF = "-vf"; 39 | public static final String THREADS = "-threads"; 40 | 41 | public static final String COPY = "copy"; 42 | 43 | public static final String FORMAT_HLS = "hls"; 44 | public static final String FORMAT_IMAGE = "image2"; 45 | public static final String FORMAT_LIB264 = "libx264"; 46 | public static final String FORMAT_ACC = "aac"; 47 | 48 | public static final String HLS_TIME = "-hls_time"; 49 | public static final String HLS_LIST_SIZE = "-hls_list_size"; 50 | public static final String HLS_WRAP = "-hls_wrap"; 51 | public static final String HLS_BASE_URL = "-hls_base_url"; 52 | 53 | 54 | public static final String H264 = "h264"; 55 | public static final String UTF8 = "utf-8"; 56 | 57 | static { 58 | String env = System.getProperty("os.name"); 59 | log.debug("current operate system :{}", env); 60 | 61 | if (StringUtils.isNotEmpty(env)) { 62 | String os = env.toLowerCase(); 63 | if (os.contains("win")) { 64 | isWin = true; 65 | } else if (os.contains("linux") || os.contains("mac")) { 66 | isLinux = true; 67 | } 68 | } 69 | //获得当前机器的CPU核数 70 | defaultIoThreads = Runtime.getRuntime().availableProcessors(); 71 | if (log.isDebugEnabled()) { 72 | log.debug("isWindows : '{}' or isLinux:'{}' defaultIoThreads:'{}'", 73 | isWin, isLinux, defaultIoThreads); 74 | } 75 | } 76 | 77 | private BaseCommandOption() { 78 | 79 | } 80 | 81 | /** 82 | * 得到ffmpeg命令参数 83 | * 84 | * @return 85 | */ 86 | public static List getFFmpegBinary() { 87 | if (ffmpegBinary == null) { 88 | if (isWin) { 89 | ffmpegBinary = Lists.newArrayList(WINCMD, WINCMDOP, FFMPEG); 90 | } else if (isLinux) { 91 | ffmpegBinary = Lists.newArrayList(LINUXCMD, FFMPEG); 92 | } 93 | } 94 | return ffmpegBinary; 95 | } 96 | 97 | /** 98 | * 得到ffprobe命令 99 | * 100 | * @return 101 | */ 102 | public static List getFFprobeBinary() { 103 | if (null == ffprobeBinary) { 104 | if (isWin) { 105 | ffprobeBinary = Lists.newArrayList(WINCMD, WINCMDOP, FFPROBE); 106 | } else if (isLinux) { 107 | ffprobeBinary = Lists.newArrayList(LINUXCMD, FFPROBE); 108 | } 109 | } 110 | return ffprobeBinary; 111 | } 112 | 113 | /** 114 | * 视频输入的命令参数 115 | * 116 | * @param input 117 | * @return 118 | */ 119 | public static List toInputCommonsCmdArrays(String input) { 120 | return Lists.newArrayList( 121 | INPUT, input 122 | ); 123 | } 124 | 125 | /** 126 | * 截图的命令参数 127 | * 128 | * @param input 129 | * @param output 130 | * @param shotSecond 131 | * @param vi 132 | * @return 133 | */ 134 | public static List toScreenshotCmdArrays(String input, String output, int shotSecond, VideoInfo vi) { 135 | if (vi != null && vi.getSize() > 0) { 136 | List commands = Lists.newArrayList(); 137 | if (vi.getDuration() < shotSecond) { 138 | shotSecond = 1; 139 | } 140 | commands.add(SS); 141 | commands.add(String.valueOf(shotSecond)); 142 | 143 | commands.addAll(toInputCommonsCmdArrays(input)); 144 | 145 | commands.addAll(getRoateCmdArrays(vi)); 146 | 147 | commands.add(T); 148 | commands.add("0.001"); 149 | commands.add(Y); 150 | commands.add(F); 151 | commands.add(FORMAT_IMAGE); 152 | 153 | 154 | commands.add(output); 155 | return commands; 156 | } 157 | return Collections.emptyList(); 158 | } 159 | 160 | /** 161 | * 转成HLS的命令参数 162 | * 163 | * @param input 164 | * @param m3u8Output 165 | * @param cutSecond 166 | * @param tsBaseUrl 167 | * @param vi 168 | * @return 169 | */ 170 | public static List toHLSCmdArrays(String input, String m3u8Output, int cutSecond, String tsBaseUrl, VideoInfo vi) { 171 | if (vi != null && vi.getSize() > 0) { 172 | List commands = Lists.newArrayList(toInputCommonsCmdArrays(input)); 173 | commands.addAll(getRoateCmdArrays(vi)); 174 | commands.addAll(Lists.newArrayList( 175 | CV, FORMAT_LIB264, 176 | CA, FORMAT_ACC, 177 | STRICT, "-2", 178 | F, FORMAT_HLS, 179 | THREADS, defaultIoThreads.toString(), 180 | HLS_TIME, String.valueOf(cutSecond), 181 | HLS_LIST_SIZE, "0", 182 | HLS_WRAP, "0", 183 | HLS_BASE_URL, tsBaseUrl, 184 | m3u8Output 185 | )); 186 | 187 | return commands; 188 | } 189 | return Collections.emptyList(); 190 | } 191 | 192 | /** 193 | * 转码到mp4的命令参数 194 | * 195 | * @param input 196 | * @param output 197 | * @param vi 198 | * @return 199 | */ 200 | public static List toMP4CmdArrays(String input, String output, VideoInfo vi) { 201 | if (vi != null && vi.getSize() > 0) { 202 | List commands = Lists.newArrayList(toInputCommonsCmdArrays(input)); 203 | commands.addAll(getRoateCmdArrays(vi)); 204 | commands.addAll(Lists.newArrayList( 205 | CV, FORMAT_LIB264, 206 | CA, FORMAT_ACC, 207 | STRICT, "-2", 208 | THREADS, defaultIoThreads.toString(), 209 | output 210 | )); 211 | return commands; 212 | } 213 | return Collections.emptyList(); 214 | } 215 | 216 | /** 217 | * 获取视频转向的参数 218 | * 219 | * @param vi 220 | * @return 221 | */ 222 | public static List getRoateCmdArrays(VideoInfo vi) { 223 | if (vi != null && vi.getSize() > 0 && vi.getRotate() > 0) { 224 | //-vf "transpose=1" -c:a copy 225 | return Lists.newArrayList( 226 | VF, "transpose=1" 227 | // CA,COPY 228 | ); 229 | } 230 | return Collections.emptyList(); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/linux,macos,maven,windows,eclipse,jetbrains+all,visualstudio,visualstudiocode,vim 3 | # Edit at https://www.gitignore.io/?templates=linux,macos,maven,windows,eclipse,jetbrains+all,visualstudio,visualstudiocode,vim 4 | 5 | ### Eclipse ### 6 | .metadata 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | .recommenders 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # PyDev specific (Python IDE for Eclipse) 25 | *.pydevproject 26 | 27 | # CDT-specific (C/C++ Development Tooling) 28 | .cproject 29 | 30 | # CDT- autotools 31 | .autotools 32 | 33 | # Java annotation processor (APT) 34 | .factorypath 35 | 36 | # PDT-specific (PHP Development Tools) 37 | .buildpath 38 | 39 | # sbteclipse plugin 40 | .target 41 | 42 | # Tern plugin 43 | .tern-project 44 | 45 | # TeXlipse plugin 46 | .texlipse 47 | 48 | # STS (Spring Tool Suite) 49 | .springBeans 50 | 51 | # Code Recommenders 52 | .recommenders/ 53 | 54 | # Annotation Processing 55 | .apt_generated/ 56 | 57 | # Scala IDE specific (Scala & Java development for Eclipse) 58 | .cache-main 59 | .scala_dependencies 60 | .worksheet 61 | 62 | ### Eclipse Patch ### 63 | # Eclipse Core 64 | .project 65 | 66 | # JDT-specific (Eclipse Java Development Tools) 67 | .classpath 68 | 69 | # Annotation Processing 70 | .apt_generated 71 | 72 | .sts4-cache/ 73 | 74 | ### JetBrains+all ### 75 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 76 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 77 | 78 | # User-specific stuff 79 | .idea/**/workspace.xml 80 | .idea/**/tasks.xml 81 | .idea/**/usage.statistics.xml 82 | .idea/**/dictionaries 83 | .idea/**/shelf 84 | 85 | # Generated files 86 | .idea/**/contentModel.xml 87 | 88 | # Sensitive or high-churn files 89 | .idea/**/dataSources/ 90 | .idea/**/dataSources.ids 91 | .idea/**/dataSources.local.xml 92 | .idea/**/sqlDataSources.xml 93 | .idea/**/dynamic.xml 94 | .idea/**/uiDesigner.xml 95 | .idea/**/dbnavigator.xml 96 | 97 | # Gradle 98 | .idea/**/gradle.xml 99 | .idea/**/libraries 100 | 101 | # Gradle and Maven with auto-import 102 | # When using Gradle or Maven with auto-import, you should exclude module files, 103 | # since they will be recreated, and may cause churn. Uncomment if using 104 | # auto-import. 105 | # .idea/modules.xml 106 | # .idea/*.iml 107 | # .idea/modules 108 | # *.iml 109 | # *.ipr 110 | 111 | # CMake 112 | cmake-build-*/ 113 | 114 | # Mongo Explorer plugin 115 | .idea/**/mongoSettings.xml 116 | 117 | # File-based project format 118 | *.iws 119 | 120 | # IntelliJ 121 | out/ 122 | 123 | # mpeltonen/sbt-idea plugin 124 | .idea_modules/ 125 | 126 | # JIRA plugin 127 | atlassian-ide-plugin.xml 128 | 129 | # Cursive Clojure plugin 130 | .idea/replstate.xml 131 | 132 | # Crashlytics plugin (for Android Studio and IntelliJ) 133 | com_crashlytics_export_strings.xml 134 | crashlytics.properties 135 | crashlytics-build.properties 136 | fabric.properties 137 | 138 | # Editor-based Rest Client 139 | .idea/httpRequests 140 | 141 | # Android studio 3.1+ serialized cache file 142 | .idea/caches/build_file_checksums.ser 143 | 144 | ### JetBrains+all Patch ### 145 | # Ignores the whole .idea folder and all .iml files 146 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 147 | 148 | .idea/ 149 | 150 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 151 | 152 | *.iml 153 | modules.xml 154 | .idea/misc.xml 155 | *.ipr 156 | 157 | # Sonarlint plugin 158 | .idea/sonarlint 159 | 160 | ### Linux ### 161 | *~ 162 | 163 | # temporary files which can be created if a process still has a handle open of a deleted file 164 | .fuse_hidden* 165 | 166 | # KDE directory preferences 167 | .directory 168 | 169 | # Linux trash folder which might appear on any partition or disk 170 | .Trash-* 171 | 172 | # .nfs files are created when an open file is removed but is still being accessed 173 | .nfs* 174 | 175 | ### macOS ### 176 | # General 177 | .DS_Store 178 | .AppleDouble 179 | .LSOverride 180 | 181 | # Icon must end with two \r 182 | Icon 183 | 184 | # Thumbnails 185 | ._* 186 | 187 | # Files that might appear in the root of a volume 188 | .DocumentRevisions-V100 189 | .fseventsd 190 | .Spotlight-V100 191 | .TemporaryItems 192 | .Trashes 193 | .VolumeIcon.icns 194 | .com.apple.timemachine.donotpresent 195 | 196 | # Directories potentially created on remote AFP share 197 | .AppleDB 198 | .AppleDesktop 199 | Network Trash Folder 200 | Temporary Items 201 | .apdisk 202 | 203 | ### Maven ### 204 | target/ 205 | pom.xml.tag 206 | pom.xml.releaseBackup 207 | pom.xml.versionsBackup 208 | pom.xml.next 209 | release.properties 210 | dependency-reduced-pom.xml 211 | buildNumber.properties 212 | .mvn/timing.properties 213 | .mvn/wrapper/maven-wrapper.jar 214 | 215 | ### Vim ### 216 | # Swap 217 | [._]*.s[a-v][a-z] 218 | [._]*.sw[a-p] 219 | [._]s[a-rt-v][a-z] 220 | [._]ss[a-gi-z] 221 | [._]sw[a-p] 222 | 223 | # Session 224 | Session.vim 225 | Sessionx.vim 226 | 227 | # Temporary 228 | .netrwhist 229 | # Auto-generated tag files 230 | tags 231 | # Persistent undo 232 | [._]*.un~ 233 | 234 | ### VisualStudioCode ### 235 | .vscode/* 236 | !.vscode/settings.json 237 | !.vscode/tasks.json 238 | !.vscode/launch.json 239 | !.vscode/extensions.json 240 | 241 | ### VisualStudioCode Patch ### 242 | # Ignore all local history of files 243 | .history 244 | 245 | ### Windows ### 246 | # Windows thumbnail cache files 247 | Thumbs.db 248 | Thumbs.db:encryptable 249 | ehthumbs.db 250 | ehthumbs_vista.db 251 | 252 | # Dump file 253 | *.stackdump 254 | 255 | # Folder config file 256 | [Dd]esktop.ini 257 | 258 | # Recycle Bin used on file shares 259 | $RECYCLE.BIN/ 260 | 261 | # Windows Installer files 262 | *.cab 263 | *.msi 264 | *.msix 265 | *.msm 266 | *.msp 267 | 268 | # Windows shortcuts 269 | *.lnk 270 | 271 | ### VisualStudio ### 272 | ## Ignore Visual Studio temporary files, build results, and 273 | ## files generated by popular Visual Studio add-ons. 274 | ## 275 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 276 | 277 | # User-specific files 278 | *.rsuser 279 | *.suo 280 | *.user 281 | *.userosscache 282 | *.sln.docstates 283 | 284 | # User-specific files (MonoDevelop/Xamarin Studio) 285 | *.userprefs 286 | 287 | # Mono auto generated files 288 | mono_crash.* 289 | 290 | # Build results 291 | [Dd]ebug/ 292 | [Dd]ebugPublic/ 293 | [Rr]elease/ 294 | [Rr]eleases/ 295 | x64/ 296 | x86/ 297 | [Aa][Rr][Mm]/ 298 | [Aa][Rr][Mm]64/ 299 | bld/ 300 | [Bb]in/ 301 | [Oo]bj/ 302 | [Ll]og/ 303 | 304 | # Visual Studio 2015/2017 cache/options directory 305 | .vs/ 306 | # Uncomment if you have tasks that create the project's static files in wwwroot 307 | #wwwroot/ 308 | 309 | # Visual Studio 2017 auto generated files 310 | Generated\ Files/ 311 | 312 | # MSTest test Results 313 | [Tt]est[Rr]esult*/ 314 | [Bb]uild[Ll]og.* 315 | 316 | # NUnit 317 | *.VisualState.xml 318 | TestResult.xml 319 | nunit-*.xml 320 | 321 | # Build Results of an ATL Project 322 | [Dd]ebugPS/ 323 | [Rr]eleasePS/ 324 | dlldata.c 325 | 326 | # Benchmark Results 327 | BenchmarkDotNet.Artifacts/ 328 | 329 | # .NET Core 330 | project.lock.json 331 | project.fragment.lock.json 332 | artifacts/ 333 | 334 | # StyleCop 335 | StyleCopReport.xml 336 | 337 | # Files built by Visual Studio 338 | *_i.c 339 | *_p.c 340 | *_h.h 341 | *.ilk 342 | *.meta 343 | *.obj 344 | *.iobj 345 | *.pch 346 | *.pdb 347 | *.ipdb 348 | *.pgc 349 | *.pgd 350 | *.rsp 351 | *.sbr 352 | *.tlb 353 | *.tli 354 | *.tlh 355 | *.tmp_proj 356 | *_wpftmp.csproj 357 | *.log 358 | *.vspscc 359 | *.vssscc 360 | .builds 361 | *.pidb 362 | *.svclog 363 | *.scc 364 | 365 | # Chutzpah Test files 366 | _Chutzpah* 367 | 368 | # Visual C++ cache files 369 | ipch/ 370 | *.aps 371 | *.ncb 372 | *.opendb 373 | *.opensdf 374 | *.sdf 375 | *.cachefile 376 | *.VC.db 377 | *.VC.VC.opendb 378 | 379 | # Visual Studio profiler 380 | *.psess 381 | *.vsp 382 | *.vspx 383 | *.sap 384 | 385 | # Visual Studio Trace Files 386 | *.e2e 387 | 388 | # TFS 2012 Local Workspace 389 | $tf/ 390 | 391 | # Guidance Automation Toolkit 392 | *.gpState 393 | 394 | # ReSharper is a .NET coding add-in 395 | _ReSharper*/ 396 | *.[Rr]e[Ss]harper 397 | *.DotSettings.user 398 | 399 | # JustCode is a .NET coding add-in 400 | .JustCode 401 | 402 | # TeamCity is a build add-in 403 | _TeamCity* 404 | 405 | # DotCover is a Code Coverage Tool 406 | *.dotCover 407 | 408 | # AxoCover is a Code Coverage Tool 409 | .axoCover/* 410 | !.axoCover/settings.json 411 | 412 | # Visual Studio code coverage results 413 | *.coverage 414 | *.coveragexml 415 | 416 | # NCrunch 417 | _NCrunch_* 418 | .*crunch*.local.xml 419 | nCrunchTemp_* 420 | 421 | # MightyMoose 422 | *.mm.* 423 | AutoTest.Net/ 424 | 425 | # Web workbench (sass) 426 | .sass-cache/ 427 | 428 | # Installshield output folder 429 | [Ee]xpress/ 430 | 431 | # DocProject is a documentation generator add-in 432 | DocProject/buildhelp/ 433 | DocProject/Help/*.HxT 434 | DocProject/Help/*.HxC 435 | DocProject/Help/*.hhc 436 | DocProject/Help/*.hhk 437 | DocProject/Help/*.hhp 438 | DocProject/Help/Html2 439 | DocProject/Help/html 440 | 441 | # Click-Once directory 442 | publish/ 443 | 444 | # Publish Web Output 445 | *.[Pp]ublish.xml 446 | *.azurePubxml 447 | # Note: Comment the next line if you want to checkin your web deploy settings, 448 | # but database connection strings (with potential passwords) will be unencrypted 449 | *.pubxml 450 | *.publishproj 451 | 452 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 453 | # checkin your Azure Web App publish settings, but sensitive information contained 454 | # in these scripts will be unencrypted 455 | PublishScripts/ 456 | 457 | # NuGet Packages 458 | *.nupkg 459 | # NuGet Symbol Packages 460 | *.snupkg 461 | # The packages folder can be ignored because of Package Restore 462 | **/[Pp]ackages/* 463 | # except build/, which is used as an MSBuild target. 464 | !**/[Pp]ackages/build/ 465 | # Uncomment if necessary however generally it will be regenerated when needed 466 | #!**/[Pp]ackages/repositories.config 467 | # NuGet v3's project.json files produces more ignorable files 468 | *.nuget.props 469 | *.nuget.targets 470 | 471 | # Microsoft Azure Build Output 472 | csx/ 473 | *.build.csdef 474 | 475 | # Microsoft Azure Emulator 476 | ecf/ 477 | rcf/ 478 | 479 | # Windows Store app package directories and files 480 | AppPackages/ 481 | BundleArtifacts/ 482 | Package.StoreAssociation.xml 483 | _pkginfo.txt 484 | *.appx 485 | *.appxbundle 486 | *.appxupload 487 | 488 | # Visual Studio cache files 489 | # files ending in .cache can be ignored 490 | *.[Cc]ache 491 | # but keep track of directories ending in .cache 492 | !?*.[Cc]ache/ 493 | 494 | # Others 495 | ClientBin/ 496 | ~$* 497 | *.dbmdl 498 | *.dbproj.schemaview 499 | *.jfm 500 | *.pfx 501 | *.publishsettings 502 | orleans.codegen.cs 503 | 504 | # Including strong name files can present a security risk 505 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 506 | #*.snk 507 | 508 | # Since there are multiple workflows, uncomment next line to ignore bower_components 509 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 510 | #bower_components/ 511 | 512 | # RIA/Silverlight projects 513 | Generated_Code/ 514 | 515 | # Backup & report files from converting an old project file 516 | # to a newer Visual Studio version. Backup files are not needed, 517 | # because we have git ;-) 518 | _UpgradeReport_Files/ 519 | Backup*/ 520 | UpgradeLog*.XML 521 | UpgradeLog*.htm 522 | ServiceFabricBackup/ 523 | *.rptproj.bak 524 | 525 | # SQL Server files 526 | *.mdf 527 | *.ldf 528 | *.ndf 529 | 530 | # Business Intelligence projects 531 | *.rdl.data 532 | *.bim.layout 533 | *.bim_*.settings 534 | *.rptproj.rsuser 535 | *- [Bb]ackup.rdl 536 | *- [Bb]ackup ([0-9]).rdl 537 | *- [Bb]ackup ([0-9][0-9]).rdl 538 | 539 | # Microsoft Fakes 540 | FakesAssemblies/ 541 | 542 | # GhostDoc plugin setting file 543 | *.GhostDoc.xml 544 | 545 | # Node.js Tools for Visual Studio 546 | .ntvs_analysis.dat 547 | node_modules/ 548 | 549 | # Visual Studio 6 build log 550 | *.plg 551 | 552 | # Visual Studio 6 workspace options file 553 | *.opt 554 | 555 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 556 | *.vbw 557 | 558 | # Visual Studio LightSwitch build output 559 | **/*.HTMLClient/GeneratedArtifacts 560 | **/*.DesktopClient/GeneratedArtifacts 561 | **/*.DesktopClient/ModelManifest.xml 562 | **/*.Server/GeneratedArtifacts 563 | **/*.Server/ModelManifest.xml 564 | _Pvt_Extensions 565 | 566 | # Paket dependency manager 567 | .paket/paket.exe 568 | paket-files/ 569 | 570 | # FAKE - F# Make 571 | .fake/ 572 | 573 | # CodeRush personal settings 574 | .cr/personal 575 | 576 | # Python Tools for Visual Studio (PTVS) 577 | __pycache__/ 578 | *.pyc 579 | 580 | # Cake - Uncomment if you are using it 581 | # tools/** 582 | # !tools/packages.config 583 | 584 | # Tabs Studio 585 | *.tss 586 | 587 | # Telerik's JustMock configuration file 588 | *.jmconfig 589 | 590 | # BizTalk build output 591 | *.btp.cs 592 | *.btm.cs 593 | *.odx.cs 594 | *.xsd.cs 595 | 596 | # OpenCover UI analysis results 597 | OpenCover/ 598 | 599 | # Azure Stream Analytics local run output 600 | ASALocalRun/ 601 | 602 | # MSBuild Binary and Structured Log 603 | *.binlog 604 | 605 | # NVidia Nsight GPU debugger configuration file 606 | *.nvuser 607 | 608 | # MFractors (Xamarin productivity tool) working folder 609 | .mfractor/ 610 | 611 | # Local History for Visual Studio 612 | .localhistory/ 613 | 614 | # BeatPulse healthcheck temp database 615 | healthchecksdb 616 | 617 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 618 | MigrationBackup/ 619 | 620 | # End of https://www.gitignore.io/api/linux,macos,maven,windows,eclipse,jetbrains+all,visualstudio,visualstudiocode,vim 621 | -------------------------------------------------------------------------------- /ffmpeg.md: -------------------------------------------------------------------------------- 1 | # FFMPEG 使用说明 2 | 3 | - [FFMPEG 使用说明](#ffmpeg-%e4%bd%bf%e7%94%a8%e8%af%b4%e6%98%8e) 4 | - [截图命令](#%e6%88%aa%e5%9b%be%e5%91%bd%e4%bb%a4) 5 | - [截取一张352x240尺寸大小,格式为jpg的图片](#%e6%88%aa%e5%8f%96%e4%b8%80%e5%bc%a0352x240%e5%b0%ba%e5%af%b8%e5%a4%a7%e5%b0%8f%e6%a0%bc%e5%bc%8f%e4%b8%bajpg%e7%9a%84%e5%9b%be%e7%89%87) 6 | - [把视频的前30帧转换成一个Animated Gif](#%e6%8a%8a%e8%a7%86%e9%a2%91%e7%9a%84%e5%89%8d30%e5%b8%a7%e8%bd%ac%e6%8d%a2%e6%88%90%e4%b8%80%e4%b8%aaanimated-gif) 7 | - [在视频的第8.01秒出截取230x240的缩略图](#%e5%9c%a8%e8%a7%86%e9%a2%91%e7%9a%84%e7%ac%ac801%e7%a7%92%e5%87%ba%e6%88%aa%e5%8f%96230x240%e7%9a%84%e7%bc%a9%e7%95%a5%e5%9b%be) 8 | - [每隔一秒截一张图](#%e6%af%8f%e9%9a%94%e4%b8%80%e7%a7%92%e6%88%aa%e4%b8%80%e5%bc%a0%e5%9b%be) 9 | - [每隔20秒截一张图](#%e6%af%8f%e9%9a%9420%e7%a7%92%e6%88%aa%e4%b8%80%e5%bc%a0%e5%9b%be) 10 | - [多张截图合并到一个文件里(2x3)每隔一千帧(秒数=1000/fps25)即40s截一张图](#%e5%a4%9a%e5%bc%a0%e6%88%aa%e5%9b%be%e5%90%88%e5%b9%b6%e5%88%b0%e4%b8%80%e4%b8%aa%e6%96%87%e4%bb%b6%e9%87%8c2x3%e6%af%8f%e9%9a%94%e4%b8%80%e5%8d%83%e5%b8%a7%e7%a7%92%e6%95%b01000fps25%e5%8d%b340s%e6%88%aa%e4%b8%80%e5%bc%a0%e5%9b%be) 11 | - [从视频中生成GIF图片](#%e4%bb%8e%e8%a7%86%e9%a2%91%e4%b8%ad%e7%94%9f%e6%88%90gif%e5%9b%be%e7%89%87) 12 | - [转换视频为图片(每帧一张图)](#%e8%bd%ac%e6%8d%a2%e8%a7%86%e9%a2%91%e4%b8%ba%e5%9b%be%e7%89%87%e6%af%8f%e5%b8%a7%e4%b8%80%e5%bc%a0%e5%9b%be) 13 | - [图片转换为视频](#%e5%9b%be%e7%89%87%e8%bd%ac%e6%8d%a2%e4%b8%ba%e8%a7%86%e9%a2%91) 14 | - [切分视频并生成M3U8文件](#%e5%88%87%e5%88%86%e8%a7%86%e9%a2%91%e5%b9%b6%e7%94%9f%e6%88%90m3u8%e6%96%87%e4%bb%b6) 15 | - [分离视频音频流](#%e5%88%86%e7%a6%bb%e8%a7%86%e9%a2%91%e9%9f%b3%e9%a2%91%e6%b5%81) 16 | - [视频解复用](#%e8%a7%86%e9%a2%91%e8%a7%a3%e5%a4%8d%e7%94%a8) 17 | - [视频转码](#%e8%a7%86%e9%a2%91%e8%bd%ac%e7%a0%81) 18 | - [视频封装](#%e8%a7%86%e9%a2%91%e5%b0%81%e8%a3%85) 19 | - [视频剪切](#%e8%a7%86%e9%a2%91%e5%89%aa%e5%88%87) 20 | - [视频录制](#%e8%a7%86%e9%a2%91%e5%bd%95%e5%88%b6) 21 | - [YUV序列播放](#yuv%e5%ba%8f%e5%88%97%e6%92%ad%e6%94%be) 22 | - [YUV序列转AVI](#yuv%e5%ba%8f%e5%88%97%e8%bd%acavi) 23 | - [常用参数说明](#%e5%b8%b8%e7%94%a8%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e) 24 | - [主要参数](#%e4%b8%bb%e8%a6%81%e5%8f%82%e6%95%b0) 25 | - [视频参数](#%e8%a7%86%e9%a2%91%e5%8f%82%e6%95%b0) 26 | - [音频参数](#%e9%9f%b3%e9%a2%91%e5%8f%82%e6%95%b0) 27 | - [使用ffmpeg合并MP4文件](#%e4%bd%bf%e7%94%a8ffmpeg%e5%90%88%e5%b9%b6mp4%e6%96%87%e4%bb%b6) 28 | - [使用ffmpeg转换flv到mp4](#%e4%bd%bf%e7%94%a8ffmpeg%e8%bd%ac%e6%8d%a2flv%e5%88%b0mp4) 29 | - [视频添加水印](#%e8%a7%86%e9%a2%91%e6%b7%bb%e5%8a%a0%e6%b0%b4%e5%8d%b0) 30 | - [水印局中](#%e6%b0%b4%e5%8d%b0%e5%b1%80%e4%b8%ad) 31 | - [视频翻转和旋转](#%e8%a7%86%e9%a2%91%e7%bf%bb%e8%bd%ac%e5%92%8c%e6%97%8b%e8%bd%ac) 32 | - [翻转](#%e7%bf%bb%e8%bd%ac) 33 | - [水平翻转语法: -vf hflip](#%e6%b0%b4%e5%b9%b3%e7%bf%bb%e8%bd%ac%e8%af%ad%e6%b3%95--vf-hflip) 34 | - [垂直翻转语法:-vf vflip](#%e5%9e%82%e7%9b%b4%e7%bf%bb%e8%bd%ac%e8%af%ad%e6%b3%95-vf-vflip) 35 | - [旋转](#%e6%97%8b%e8%bd%ac) 36 | - [将视频顺时针旋转90度](#%e5%b0%86%e8%a7%86%e9%a2%91%e9%a1%ba%e6%97%b6%e9%92%88%e6%97%8b%e8%bd%ac90%e5%ba%a6) 37 | - [将视频水平翻转(左右翻转)](#%e5%b0%86%e8%a7%86%e9%a2%91%e6%b0%b4%e5%b9%b3%e7%bf%bb%e8%bd%ac%e5%b7%a6%e5%8f%b3%e7%bf%bb%e8%bd%ac) 38 | - [顺时针旋转90度并水平翻转](#%e9%a1%ba%e6%97%b6%e9%92%88%e6%97%8b%e8%bd%ac90%e5%ba%a6%e5%b9%b6%e6%b0%b4%e5%b9%b3%e7%bf%bb%e8%bd%ac) 39 | - [添加字幕](#%e6%b7%bb%e5%8a%a0%e5%ad%97%e5%b9%95) 40 | - [嵌入字幕](#%e5%b5%8c%e5%85%a5%e5%ad%97%e5%b9%95) 41 | 42 | ## 截图命令 43 | 44 | ### 截取一张352x240尺寸大小,格式为jpg的图片 45 | 46 | ```bash 47 | ffmpeg -i input_file -y -f image2 -t 0.001 -s 352x240 output.jpg 48 | ``` 49 | 50 | ### 把视频的前30帧转换成一个Animated Gif 51 | 52 | ```bash 53 | ffmpeg -i input_file -vframes 30 -y -f gif output.gif 54 | ``` 55 | 56 | ### 在视频的第8.01秒出截取230x240的缩略图 57 | 58 | ```bash 59 | ffmpeg -i input_file -y -f mjpeg -ss 8 -t 0.001 -s 320x240 output.jpg 60 | ``` 61 | 62 | ### 每隔一秒截一张图 63 | 64 | ```bash 65 | ffmpeg -i out.mp4 -f image2 -vf fps=fps=1 out%d.png 66 | ``` 67 | 68 | ### 每隔20秒截一张图 69 | 70 | ```bash 71 | ffmpeg -i out.mp4 -f image2 -vf fps=fps=1/20 out%d.png 72 | ``` 73 | 74 | ### 多张截图合并到一个文件里(2x3)每隔一千帧(秒数=1000/fps25)即40s截一张图 75 | 76 | ``` 77 | ffmpeg -i out.mp4 -frames 3 -vf "select=not(mod(n\,1000)),scale=320:240,tile=2x3" out.png 78 | ``` 79 | 80 | ### 从视频中生成GIF图片 81 | 82 | ```bash 83 | ffmpeg -i out.mp4 -t 10 -pix_fmt rgb24 out.gif 84 | ``` 85 | 86 | ### 转换视频为图片(每帧一张图) 87 | 88 | ```bash 89 | ffmpeg -i out.mp4 out%4d.png 90 | ``` 91 | 92 | ### 图片转换为视频 93 | 94 | ```bash 95 | ffmpeg -f image2 -i out%4d.png -r 25 video.mp4 96 | ``` 97 | 98 | ## 切分视频并生成M3U8文件 99 | 100 | ```bash 101 | ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict -2 -f hls -hls_time 20 -hls_list_size 0 -hls_wrap 0 output.m3u8 102 | ``` 103 | 104 | 相关参数说明: 105 | 106 | ```bash 107 | -i 输入视频文件 108 | -c:v 输出视频格式 109 | -c:a 输出音频格式 110 | -strict 111 | -f hls 输出视频为HTTP Live Stream(M3U8) 112 | -hls_time 设置每片的长度,默认为2,单位为秒 113 | -hls_list_size 设置播放列表保存的最多条目,设置为0会保存所有信息,默认为5 114 | -hls_wrap 设置多少片之后开始覆盖,如果设置为0则不会覆盖,默认值为0。这个选项能够避免在磁盘上存储过多的片,而且能够限制写入磁盘的最多片的数量。 115 | ``` 116 | 117 | 注意,播放列表的`sequence number`对每个`segment`来说都必须是唯一的,而且它不能和片的文件名(当使用`wrap`选项时,文件名可能会重复使用)混淆。 118 | 119 | ## 分离视频音频流 120 | 121 | ```bash 122 | # 分离视频流 123 | ffmpeg -i input_file -vcodec copy -an output_file_video 124 | 125 | # 分离音频流 126 | ffmpeg -i input_file -acodec copy -vn output_file_audio 127 | ``` 128 | 129 | ## 视频解复用 130 | 131 | ```bash 132 | ffmpeg -i test.mp4 -vcoder copy -an -f m4v test.264 133 | ffmpeg -i test.avi -vcoder copy -an -f m4v test.264 134 | ``` 135 | 136 | ## 视频转码 137 | 138 | ```bash 139 | # 转码为码流原始文件 140 | ffmpeg -i test.mp4 -vcoder h264 -s 352*278 -an -f m4v test.264 141 | 142 | # 转码为码流原始文件 143 | ffmpeg -i test.mp4 -vcoder h264 -bf 0 -g 25 -s 352-278 -an -f m4v test.264 144 | 145 | # 转码为封装文件 -bf B帧数目控制, -g 关键帧间隔控制, -s 分辨率控制 146 | ffmpeg -i test.avi -vcoder mpeg4 -vtag xvid -qsame test_xvid.avi 147 | ``` 148 | 149 | ## 视频封装 150 | 151 | ```bash 152 | ffmpeg -i video_file -i audio_file -vcoder copy -acodec copy output_file 153 | ``` 154 | 155 | ## 视频剪切 156 | 157 | ```bash 158 | # 视频截图 159 | ffmpeg -i test.avi -r 1 -f image2 image.jpeg 160 | 161 | # 剪切视频 -r 提取图像频率, -ss 开始时间, -t 持续时间 162 | ffmpeg -i input.avi -ss 0:1:30 -t 0:0:20 -vcoder copy -acoder copy output.avi 163 | ``` 164 | 165 | ## 视频录制 166 | 167 | ```bash 168 | ffmpeg -i rtsp://hostname/test -vcoder copy out.avi 169 | ``` 170 | 171 | ## YUV序列播放 172 | 173 | ```bash 174 | ffplay -f rawvideo -video_size 1920x1080 input.yuv 175 | ``` 176 | 177 | ## YUV序列转AVI 178 | 179 | ```bash 180 | ffmpeg -s w*h -pix_fmt yuv420p -i input.yuv -vcoder mpeg4 output.avi 181 | ``` 182 | 183 | ### 常用参数说明 184 | 185 | #### 主要参数 186 | 187 | ```bash 188 | -i 设定输入流 189 | -f 设定输出格式 190 | -ss 开始时间 191 | ``` 192 | 193 | #### 视频参数 194 | 195 | ```bash 196 | -b 设定视频流量,默认是200Kbit/s 197 | -s 设定画面的宽和高 198 | -aspect 设定画面的比例 199 | -vn 不处理视频 200 | -vcoder 设定视频的编码器,未设定时则使用与输入流相同的编解码器 201 | ``` 202 | 203 | ### 音频参数 204 | 205 | ```bash 206 | -ar 设定采样率 207 | -ac 设定声音的Channel数 208 | -acodec 设定沈阳的Channel数 209 | -an 不处理音频 210 | ``` 211 | 212 | ## 使用ffmpeg合并MP4文件 213 | 214 | ```bash 215 | ffmpeg -i "Apache Sqoop Tutorial Part 1.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts 216 | ffmpeg -i "Apache Sqoop Tutorial Part 2.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts 217 | ffmpeg -i "Apache Sqoop Tutorial Part 3.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate3.ts 218 | ffmpeg -i "Apache Sqoop Tutorial Part 4.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate4.ts 219 | ffmpeg -i "concat:intermediate1.ts|intermediate2.ts|intermediate3.ts|intermediate4.ts" -c copy -bsf:a aac_adtstoasc "Apache Sqoop Tutorial.mp4" 220 | ``` 221 | 222 | ## 使用ffmpeg转换flv到mp4 223 | 224 | ```bash 225 | ffmpeg -i out.flv -vcodec copy -acodec copy out.mp4 226 | ``` 227 | 228 | ## 视频添加水印 229 | 230 | ### 水印局中 231 | 232 | ```bash 233 | ffmpeg -i out.mp4 -i sxyx2008@163.com.gif -filter_complex overlay="(main_w/2)-(overlay_w/2):(main_h/2)-(overlay_h)/2" output.mp4 234 | ``` 235 | 236 | 参数解释 237 | 238 | - -i out.mp4(视频源) 239 | - -i sxyx2008@163.com.gif(水印图片) 240 | - overlay 水印的位置 241 | - output.mp4 输出文件 242 | 243 | ## 视频翻转和旋转 244 | 245 | ### 翻转 246 | 247 | #### 水平翻转语法: -vf hflip 248 | 249 | ```bahs 250 | ffplay -i out.mp4 -vf hflip 251 | ``` 252 | 253 | #### 垂直翻转语法:-vf vflip 254 | 255 | ```bash 256 | ffplay -i out.mp4 -vf vflip 257 | ``` 258 | 259 | ### 旋转 260 | 261 | 语法:`transpose={0,1,2,3}` 262 | 263 | - 0:逆时针旋转90°然后垂直翻转 264 | - 1:顺时针旋转90° 265 | - 2:逆时针旋转90° 266 | - 3:顺时针旋转90°然后水平翻转 267 | 268 | ### 将视频顺时针旋转90度 269 | 270 | ```bash 271 | ffplay -i out.mp4 -vf transpose=1 272 | ``` 273 | 274 | ### 将视频水平翻转(左右翻转) 275 | 276 | ```bash 277 | ffplay -i out.mp4 -vf hflip 278 | ``` 279 | 280 | ### 顺时针旋转90度并水平翻转 281 | 282 | ```bash 283 | ffplay -i out.mp4 -vf transpose=1,hflip 284 | ``` 285 | 286 | ## 添加字幕 287 | 288 | 有的时候你需要给视频加一个字幕(subtitle),使用ffmpeg也可以做。一般我们见到的字幕以srt字幕为主,在ffmpeg里需要首先将srt字幕转化为ass字幕,然后就可以集成到视频中了(不是单独的字幕流,而是直接改写视频流)。 289 | 290 | ```bash 291 | ffmpeg -i my_subtitle.srt my_subtitle.ass 292 | ffmpeg -i inputfile.mp4 -vf ass=my_subtitle.ass outputfile.mp4 293 | ``` 294 | 295 | 但是值得注意的是: 296 | 297 | > `my_subtitle.srt`需要使用`UTF8`编码,老外不会注意到这一点,但是中文这是必须要考虑的; 298 | 299 | 将字幕直接写入视频流需要将每个字符渲染到画面上,因此有一个字体的问题,在`ass`文件中会指定一个缺省字体,例如`Arial`,但是我们首先需要让`ffmpeg`能找到字体文件,不然文字的渲染就无从谈起了。`ffmpeg`使用了`fontconfig`来设置字体配置。你需要首先设置一下`FONTCONFIG_PATH`或者`FONTCONFIG_FILE`环境变量,不然`fontconfig`是无法找到配置文件的,这一点请参看这篇文章,如果你设置的是`FONTCONFIG_PATH`,那把配置文件保存为`%FONTCONFIG_PATH%/font.conf`即可,然后你可以在`font.conf`文件中配置字体文件的路径之类的。 300 | 301 | `Windows`下为`fontconfig`设置如下的环境变量 302 | 303 | ```bash 304 | FC_CONFIG_DIR=C:\ffmpeg 305 | FONTCONFIG_FILE=font.conf 306 | FONTCONFIG_PATH=C:\ffmpeg 307 | PATH=C:\ffmpeg\bin;%PATH% 308 | ``` 309 | 310 | 下面是一个简单的`Windows`版`font.conf`文件。 311 | 312 | ```xml 313 | 314 | 315 | 316 | C:\WINDOWS\Fonts 317 | 318 | 319 | mono 320 | monospace 321 | 322 | 323 | 324 | sans-serif 325 | serif 326 | monospace 327 | sans-serif 328 | 329 | 330 | 331 | Times 332 | Times New Roman 333 | serif 334 | 335 | 336 | Helvetica 337 | Arial 338 | sans 339 | 340 | 341 | Courier 342 | Courier New 343 | monospace 344 | 345 | 346 | serif 347 | Times New Roman 348 | 349 | 350 | sans 351 | Arial 352 | 353 | 354 | monospace 355 | Andale Mono 356 | 357 | 358 | 359 | Courier New 360 | 361 | 362 | monospace 363 | 364 | 365 | 366 | 367 | Courier 368 | 369 | 370 | monospace 371 | 372 | 373 | 374 | 375 | ``` 376 | 377 | 下面这个是`Linux`系统下改版过来的 378 | 379 | ```xml 380 | 381 | 382 | 383 | 384 | 387 | C:/Windows/Fonts 388 | 391 | 394 | 395 | mono 396 | monospace 397 | 398 | 399 | 403 | fontconfig/fonts.conf 404 | 405 | 409 | conf.d 410 | local.conf 411 | 412 | 417 | 418 | Times 419 | Times New Roman 420 | serif 421 | 422 | 423 | Helvetica 424 | Arial 425 | sans 426 | 427 | 428 | Courier 429 | Courier New 430 | monospace 431 | 432 | 433 | 438 | 439 | serif 440 | Times New Roman 441 | 442 | 443 | sans 444 | Arial 445 | 446 | 447 | monospace 448 | Andale Mono 449 | 450 | 451 | ``` 452 | 453 | 参考: 454 | 455 | - http://blog.raphaelzhang.com/2013/04/video-streaming-and-ffmpeg-transcoding/ 456 | 457 | ## 嵌入字幕 458 | 459 | 在一个MP4文件里面添加字幕,不是把 .srt 字幕文件集成到 MP4 文件里,而是在播放器里选择字幕,这种集成字幕比较简单,速度也相当快 460 | 461 | ```bash 462 | ffmpeg -i input.mp4 -i subtitles.srt -c:s mov_text -c:v copy -c:a copy output.mp4 463 | ``` 464 | 465 | 希望字幕直接显示出来,其实也不难 466 | 467 | ```bash 468 | ffmpeg -i subtitle.srt subtitle.ass 469 | ffmpeg -i input.mp4 -vf ass=subtitle.ass output.mp4 470 | ``` 471 | 472 | 参考: 473 | 474 | - http://blog.neten.de/posts/2013/10/06/use-ffmpeg-to-burn-subtitles-into-the-video/ 475 | --------------------------------------------------------------------------------