最近老是要生成一些gif图片,图片转gif或者是视频转gif,网上的工具软件又要钱,于是自己写了一个,记录一下代码。
https://github.com/microshow/RxFFmpeg
这是一个优秀的开源ffmpeg库,可自己输入命令执行,还有进度返回,以及停止命令操作。平台架构全,支持 armeabi-v7a, arm64-v8a, x86, x86_64。一般我们用到armeabi-v7a, arm64-v8a就行了,要不是小米应用市场年底明确要求要64位架构,也懒得引用这么多。
视频转gif
1629446394831.gif一般我们的命令就是直接将mp4转gif,如下:
val text =
"ffmpeg -y -ss ${mMinPercentage} -t ${mMaxPercentage-mMinPercentage} -i ${resultPhotos!!.get(0).path} -r 15 -preset superfast /storage/emulated/0/1.gif"
val commands = text.split(" ").toTypedArray()
开始执行FFmpeg命令
RxFFmpegInvoke.getInstance()
.runCommandRxJava(commands)
.subscribe(MyRxFFmpegSubscriber(this@VideoToGifActivity))
${mMinPercentage} -t ${mMaxPercentage-mMinPercentage}
是时间设置比如:3 -t 4 就是从第三秒开始,往后延续4秒也就是3秒到7秒之间。这个要理解
-i ${resultPhotos!!.get(0).path}
指的是视频地址,这里要视频全路径,比如:/storage/emulated/0/1.mp4
-r 15
是指15帧
-preset superfast
是设置ffmpeg执行速度还能设置 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo,速度越慢,越高质量。
但是这样生成出来的gif是很模糊的。
加入调色板
public void gotoPalettegen(){
val text =
"ffmpeg -ss ${mMinPercentage} -t ${mMaxPercentage - mMinPercentage} -i ${resultPhotos!!.get(0).path} -r ${mFrame} -vf fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos,palettegen -y /storage/emulated/0/JJGif/pt.png"
Log.d("yanjin","text= ${text}")
val commands = text.split(" ").toTypedArray()
//开始执行FFmpeg命令
RxFFmpegInvoke.getInstance()
.runCommandRxJava(commands)
.subscribe(object : RxFFmpegSubscriber() {
override fun onError(message: String?) {
if (showLoading != null) {
showLoading!!.dismissAllowingStateLoss()
}
}
override fun onFinish() {
goToGif()
}
override fun onProgress(p: Int, progressTime: Long) {}
override fun onCancel() {
if (showLoading != null) {
showLoading!!.dismissAllowingStateLoss()
}
}
})
}
public void gotoGif(){
val text =
"ffmpeg -ss ${mMinPercentage} -t ${mMaxPercentage - mMinPercentage} -i ${resultPhotos!!.get(0).path} -r ${mFrame} -vf fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos,palettegen -y /storage/emulated/0/JJGif/pt.png"
Log.d("yanjin","text= ${text}")
val commands = text.split(" ").toTypedArray()
//开始执行FFmpeg命令
RxFFmpegInvoke.getInstance()
.runCommandRxJava(commands)
.subscribe(object : RxFFmpegSubscriber() {
override fun onError(message: String?) {
if (showLoading != null) {
showLoading!!.dismissAllowingStateLoss()
}
}
override fun onFinish() {
val text =
"ffmpeg -v warning -ss ${mMinPercentage} -t ${mMaxPercentage - mMinPercentage} -i ${resultPhotos!!.get(0).path} " +
"-i /storage/emulated/0/JJGif/pt.png -r ${mFrame} " +
"-lavfi fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos[x];[x][1:v]paletteuse -y /storage/emulated/0/JJGif/${resultPhotos!!.get(0).name}.gif"
Log.d("yanjin", "text= ${text}")
val commands = text.split(" ").toTypedArray()
//开始执行FFmpeg命令
RxFFmpegInvoke.getInstance()
.runCommandRxJava(commands)
.subscribe(MyRxFFmpegSubscriber(this@VideoToGifActivity))
}
override fun onProgress(p: Int, progressTime: Long) {}
override fun onCancel() {
if (showLoading != null) {
showLoading!!.dismissAllowingStateLoss()
}
}
})
}
class MyRxFFmpegSubscriber(activity: VideoToGifActivity):RxFFmpegSubscriber() {
private var weakReference:WeakReference<VideoToGifActivity>? = null
init {
weakReference = WeakReference(activity)
}
override fun onError(message: String?) {
if(weakReference == null || weakReference!!.get() == null){
return
}
if (weakReference!!.get()!!.showLoading != null) {
weakReference!!.get()!!.showLoading!!.dismissAllowingStateLoss()
}
}
override fun onFinish() {
if(weakReference == null || weakReference!!.get() == null){
return
}
weakReference!!.get()!!.mTvNext?.postDelayed(object : Runnable {
override fun run() {
Toast.makeText(App.getInstance(), "已保存gif到${weakReference!!.get()!!.path}", Toast.LENGTH_LONG)
if (weakReference!!.get()!!.showLoading != null) {
weakReference!!.get()!!.showLoading!!.dismissAllowingStateLoss()
}
weakReference!!.get()!!.gotoFinishActivity()
}
}, 1000)
}
override fun onProgress(progress: Int, progressTime: Long) {
if(weakReference == null || weakReference!!.get() == null){
return
}
var totalProgressTime = (weakReference!!.get()!!.mMaxPercentage - weakReference!!.get()!!.mMinPercentage) * 1000000
if (weakReference!!.get()!!.showLoading != null) {
var progress: Float =
(progressTime.toFloat() / totalProgressTime.toFloat()).toFloat()
if (progress > 0.95f) {
weakReference!!.get()!!.showLoading!!.onProgressChanged(1.0f)
} else {
weakReference!!.get()!!.showLoading!!.onProgressChanged(progress)
}
}
}
override fun onCancel() {
if(weakReference == null || weakReference!!.get() == null){
return
}
if (weakReference!!.get()!!.showLoading != null) {
weakReference!!.get()!!.showLoading!!.dismissAllowingStateLoss()
}
}
}
我们在执行每一个视频转gif的时候,都要先生成这个视频的调色图片也就是gotoPalettegen方法。
我们分析下他的命令
${mMinPercentage} -t ${mMaxPercentage - mMinPercentage}
老样子,还是时间设置
-i ${resultPhotos!!.get(0).path}
还是视频地址
-r ${mFrame}
还是帧率
-vf fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos,palettegen
这个就是生成调色板的图。值得一提的是设置宽高,就是如下这段代码:
scale=${mCurrentWidth}:${mCurrentHeight}:
如果我们写生固定数字,比如:scale=500:500: 那么他就生成是500*500的图片,如果视屏比例不是1:1,那么就会产生压缩,如果不想被压缩,那么ffmpeg还支持宽度固定,高度自适应比如:scale=360:-1: 把高度设置为-1时,就能设置这个功能了。
-y /storage/emulated/0/JJGif/pt.png
就是调色板存放路径了,作为一个程序员,尤其是android程序员,权限问题一定不能忘记。
然后在完成后再执行真正的生成gif代码也就是gotoGif方法
老样子,我们看命令
老代码就不说了,说说不同点
-i /storage/emulated/0/JJGif/pt.png
这就是刚刚生成的调色板图片,我们引入他
然后设置全局应用
-lavfi fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos[x];[x][1:v]paletteuse
当然还有局部应用,大家可以去网上查查。
-y /storage/emulated/0/JJGif/${resultPhotos!!.get(0).name}.gif
这个就是生成gif了。
代码麻烦了一点,多了层嵌套。
图片转gif
我们可以用ffmpeg,命令简单,但是我这里再介绍一个好用的开源库Gifflen
https://github.com/lchad/Gifflen-Android
var list: ArrayList<File> = ArrayList()
var mGiffle = Gifflen.Builder()
.color(0)
.delay(mCurrentSpeed) //每相邻两帧之间播放的时间间隔.
.quality(mCurrentQuality)
.width(mCurrentWidth) //生成Gif文件的宽度(像素).
.height(mCurrentHeight) //生成Gif文件的高度(像素).
.bgColor(mCurrentColor)
.listener(object : Gifflen.OnEncodeFinishListener {
override fun onEncodeFinish(path: String?) {
Toast.makeText(App.getInstance(), "已保存gif到$path", Toast.LENGTH_LONG)
.show()
if(showLoading != null){
showLoading!!.dismissAllowingStateLoss()
}
dismissAllowingStateLoss()
gotoFinishActivity(path)
}
override fun onEncodeProgress(progress: Float) {
if(showLoading != null){
showLoading!!.onProgressChanged(progress)
}
}
})
.build()
resultPhotos?.forEach {
list.add(File(it.path))
}
//线程池异步调用
ExecutorsTools.getInstance().addTask(RunnableImp(mGiffle,list))
网友评论