图片转字符图片(三)

序言

这个是从抖音上学来的,一开始刷抖音,遇到不少字符串跳舞的视频,因此来实践一下

主要分为三个部分

  1. 静态图片转静态图片
  2. gif转gif
  3. 视频转视频

视频转视频

主要用到了FFmpeg这个工具,利用命令对视频文件进行操作。首先根据自己调的参数进行图片的截取(本文的是1秒10帧的参数),图片转换,然后分离音频,最后字符图片和音频合成目标视频。

FFmpeg的代码库:

https://github.com/FFmpeg/FFmpeg

FFmpeg下载地址:

https://ffmpeg.org/download.html

本位使用的版本:

https://blog-lossingdawn.oss-cn-shanghai.aliyuncs.com/img2text/ffmpeg-20180201-b1af0e2-win64-static.zip

效果如下

没做细致的调整,视频的转换可以调调参数

Watch the video

Watch the video

测试代码

1
2
3
4
5
6
7
8
@Test
public static void videoTest() {
String srcVideoPath = "F:/123/123.mp4";
String tarImagePath = "F:/123/mp/";
String tarAudioPath = "F:/123/mp/audio.aac";
String tarVideoPath = "F:/123/1234.mp4";
VideoUtil.readVideo(srcVideoPath,tarImagePath,tarAudioPath,tarVideoPath);
}

代码如下

主要用到的几个命令,其他按帧截图命令参考文末链接4:

1
2
3
4
5
6
// 截图
ffmpeg -ss 10 -i input.flv -y -f image2 -vframes 100 -s 352x240 b-%03d.jpg
// 分离音频
ffmpeg -i 3.mp4 -vn -y -acodec copy 3.aac
// 合成视频
ffmpeg -threads2 -y -r 10 -i /tmpdir/image%04d.jpg -i audio.mp3 -absf aac_adtstoasc output.mp4

环境:

JDK 1.8

完整代码如下

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
// ffmpeg -ss 10 -i input.flv -y -f image2  -vframes 100 -s 352x240 b-%03d.jpg  
/**
* ffmpeg 截图,并指定图片的大小
*
* @param srcVideoPath
* @param tarImagePath
* 截取后图片路径
* @param width
* 截图的宽
* @param hight
* 截图的高
* @param offsetValue
* 表示相对于文件开始处的时间偏移值 可以是分秒
* @param vframes
* 表示截图的桢数
*
* @return
*/
public static boolean processFfmpegImage(String srcVideoPath, String tarImagePath, int width, int hight,
float offsetValue, float vframes) {
if (!checkfile(srcVideoPath)) {
System.out.println("【" + srcVideoPath + "】 不存在 !");
// logger.error("【" + srcVideoPath + "】 不存在 !");
return false;
}
List<String> commend = new java.util.ArrayList<String>();

commend.add(ffmpegPath);

commend.add("-i");

commend.add(srcVideoPath);

commend.add("-y");

commend.add("-f");

commend.add("image2");

commend.add("-ss");

commend.add(offsetValue + ""); // 在视频的某个插入时间截图,例子为5秒后

// commend.add("-vframes");

commend.add("-t");// 添加参数"-t",该参数指定持续时间

commend.add(vframes + ""); // 截图的桢数,添加持续时间为1毫秒

commend.add("-s");

commend.add(width + "x" + hight); // 截图的的大小

commend.add(tarImagePath);

try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
builder.redirectErrorStream(true);
// builder.redirectOutput(new File("F:/123/log/log.log"));
Process process = builder.start();
doWaitFor(process);
process.destroy();
if (!checkfile(tarImagePath)) {
System.out.println(tarImagePath + " is not exit! processFfmpegImage 转换不成功 !");
// logger.info(tarImagePath + " is not exit! processFfmpegImage
// 转换不成功 !");
return false;
}
return true;
} catch (Exception e) {
e.printStackTrace();
System.out.println("【" + srcVideoPath + "】 processFfmpegImage 转换不成功 !");
// logger.error("【" + srcVideoPath + "】 processFfmpegImage 转换不成功
// !");
return false;
}
}

public static boolean processFfmpegAudio(String srcVideoPath, String tarAudioPath) {
if (!checkfile(srcVideoPath)) {
System.out.println("【" + srcVideoPath + "】 不存在 !");
// logger.error("【" + srcVideoPath + "】 不存在 !");
return false;
}
// https://blog.csdn.net/xiaocao9903/article/details/53420519
// ffmpeg -i 3.mp4 -vn -y -acodec copy 3.aac
// ffmpeg -i 3.mp4 -vn -y -acodec copy 3.m4a

List<String> commend = new java.util.ArrayList<String>();

commend.add(ffmpegPath);

commend.add("-i");

commend.add(srcVideoPath);

commend.add("-vn");

commend.add("-y");

commend.add("-acodec");

commend.add("copy"); // 在视频的某个插入时间截图,例子为5秒后

commend.add(tarAudioPath);

try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
builder.redirectErrorStream(true);
Process process = builder.start();
doWaitFor(process);
process.destroy();
if (!checkfile(tarAudioPath)) {
System.out.println(tarAudioPath + " is not exit! processFfmpegAudio 转换不成功 !");
return false;
}
return true;
} catch (Exception e) {
e.printStackTrace();
System.out.println("【" + srcVideoPath + "】 processFfmpegAudio 转换不成功 !");
return false;
}
}

/**
* ffmpeg 合成视频
*
* @param srcVideoPath
* @param tarImagePath
* 截取后图片路径
* @param width
* 截图的宽
* @param hight
* 截图的高
* @param offsetValue
* 表示相对于文件开始处的时间偏移值 可以是分秒
* @param vframes
* 表示截图的桢数
*
* @return
*/
public static boolean processFfmpegVideo(String imagePath, String audioPath, String tarVideoPath, int step) {
// https://blog.csdn.net/wangshuainan/article/details/77914508?fps=1&locationNum=4
// 带音频
// ffmpeg -threads2 -y -r 10 -i /tmpdir/image%04d.jpg -i audio.mp3 -absf
// aac_adtstoasc output.mp4

List<String> commend = new java.util.ArrayList<String>();

commend.add(ffmpegPath);

commend.add("-threads");

commend.add("2");

commend.add("-y");

commend.add("-r");

commend.add(step + "");

commend.add("-i");

commend.add(imagePath); // 图片

commend.add("-i");

commend.add(audioPath);

commend.add("-absf");//

commend.add("aac_adtstoasc"); //

commend.add(tarVideoPath);

try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
builder.redirectErrorStream(true);
builder.redirectOutput(new File("F:/123/log/log.log"));
Process process = builder.start();
doWaitFor(process);
process.destroy();
if (!checkfile(tarVideoPath)) {
System.out.println(tarVideoPath + " is not exit! processFfmpegVideo 转换不成功 !");
return false;
}
return true;
} catch (Exception e) {
e.printStackTrace();
System.out.println("【" + tarVideoPath + "】 processFfmpegVideo 转换不成功 !");
return false;
}
}

源码地址:

https://github.com/Ruffianjiang/java4fun/tree/master/img2text

参考:

  1. https://blog.csdn.net/i_likechard/article/details/79032931
  2. https://blog.csdn.net/xiaocao9903/article/details/53420519
  3. https://blog.csdn.net/wangshuainan/article/details/77914508?fps=1&locationNum=4
  4. https://blog.csdn.net/yourijing/article/details/50786758