You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
FFmpegAndroid/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.kt

481 lines
20 KiB

package com.frank.ffmpeg.activity
import android.annotation.SuppressLint
import android.graphics.Color
import android.media.MediaMetadataRetriever
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Message
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.frank.ffmpeg.FFmpegCmd
import com.frank.ffmpeg.R
import com.frank.ffmpeg.adapter.WaterfallAdapter
import com.frank.ffmpeg.format.VideoLayout
import com.frank.ffmpeg.gif.HighQualityGif
import com.frank.ffmpeg.handler.FFmpegHandler
import com.frank.ffmpeg.tool.JsonParseTool
import com.frank.ffmpeg.util.BitmapUtil
import com.frank.ffmpeg.util.FFmpegUtil
import com.frank.ffmpeg.util.FileUtil
import java.io.File
import java.util.ArrayList
import com.frank.ffmpeg.handler.FFmpegHandler.MSG_BEGIN
import com.frank.ffmpeg.handler.FFmpegHandler.MSG_FINISH
import com.frank.ffmpeg.handler.FFmpegHandler.MSG_PROGRESS
import com.frank.ffmpeg.listener.OnItemClickListener
/**
* video process by FFmpeg command
* Created by frank on 2018/1/25.
*/
class VideoHandleActivity : BaseActivity() {
private var layoutVideoHandle: RecyclerView? = null
private var layoutProgress: LinearLayout? = null
private var txtProgress: TextView? = null
private var currentPosition: Int = 0
private var ffmpegHandler: FFmpegHandler? = null
private val appendPath = PATH + File.separator + "snow.mp4"
private val outputPath1 = PATH + File.separator + "output1.ts"
private val outputPath2 = PATH + File.separator + "output2.ts"
private val listPath = PATH + File.separator + "listFile.txt"
private var list :List<String> ?= null
private var isJointing = false
@SuppressLint("HandlerLeak")
private val mHandler = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
MSG_BEGIN -> {
layoutProgress!!.visibility = View.VISIBLE
layoutVideoHandle!!.visibility = View.GONE
}
MSG_FINISH -> {
layoutProgress!!.visibility = View.GONE
layoutVideoHandle!!.visibility = View.VISIBLE
if (isJointing) {
isJointing = false
FileUtil.deleteFile(outputPath1)
FileUtil.deleteFile(outputPath2)
FileUtil.deleteFile(listPath)
}
if (!outputPath.isNullOrEmpty() && !this@VideoHandleActivity.isDestroyed) {
showToast("Save to:$outputPath")
outputPath = ""
}
}
MSG_PROGRESS -> {
val progress = msg.arg1
val duration = msg.arg2
if (progress > 0) {
txtProgress!!.visibility = View.VISIBLE
val percent = if (duration > 0) "%" else ""
val strProgress = progress.toString() + percent
txtProgress!!.text = strProgress
} else {
txtProgress!!.visibility = View.INVISIBLE
}
}
else -> {
}
}
}
}
override val layoutId: Int
get() = R.layout.activity_video_handle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// hideActionBar()
intView()
ffmpegHandler = FFmpegHandler(mHandler)
}
private fun intView() {
layoutProgress = getView(R.id.layout_progress)
txtProgress = getView(R.id.txt_progress)
list = listOf(
getString(R.string.video_transform),
getString(R.string.video_cut),
getString(R.string.video_concat),
getString(R.string.video_screen_shot),
getString(R.string.video_water_mark),
getString(R.string.video_remove_logo),
getString(R.string.video_from_photo),
getString(R.string.video_to_gif),
getString(R.string.video_multi),
getString(R.string.video_reverse),
getString(R.string.video_denoise),
getString(R.string.video_image),
getString(R.string.video_pip),
getString(R.string.video_speed),
getString(R.string.video_thumbnail),
getString(R.string.video_subtitle),
getString(R.string.video_rotate),
getString(R.string.video_gray),
getString(R.string.video_zoom))
layoutVideoHandle = findViewById(R.id.list_video_item)
val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
layoutVideoHandle?.layoutManager = layoutManager
val adapter = WaterfallAdapter(list)
adapter.setOnItemClickListener(object : OnItemClickListener {
override fun onItemClick(position: Int) {
currentPosition = position
if (getString(R.string.video_from_photo) == (list as List<String>)[position]) {
handlePhoto()
} else {
selectFile()
}
}
})
layoutVideoHandle?.adapter = adapter
}
override fun onViewClick(view: View) {
}
override fun onSelectedFile(filePath: String) {
doHandleVideo(filePath)
}
/**
* Using FFmpeg cmd to handle video
*
* @param srcFile srcFile
*/
private fun doHandleVideo(srcFile: String) {
var commandLine: Array<String>? = null
if (!FileUtil.checkFileExist(srcFile)) {
return
}
if (!FileUtil.isVideo(srcFile)
&& (list as List<String>)[currentPosition] != getString(R.string.video_zoom)) {
showToast(getString(R.string.wrong_video_format))
return
}
val suffix = FileUtil.getFileSuffix(srcFile)
if (suffix == null || suffix.isEmpty()) {
return
}
when (currentPosition) {
0 //transform format
-> {
outputPath = PATH + File.separator + "transformVideo.mp4"
commandLine = FFmpegUtil.transformVideo(srcFile, outputPath)
}
1 //cut video
-> {
outputPath = PATH + File.separator + "cutVideo" + suffix
val startTime = 5.5f
val duration = 20.0f
commandLine = FFmpegUtil.cutVideo(srcFile, startTime, duration, outputPath)
}
2 //concat video together
-> concatVideo(srcFile)
3 //video snapshot
-> {
outputPath = PATH + File.separator + "screenShot.jpg"
val time = 10.5f
commandLine = FFmpegUtil.screenShot(srcFile, time, outputPath)
}
4 //add watermark to video
-> {
// the unit of bitRate is kb
var bitRate = 500
val retriever = MediaMetadataRetriever()
retriever.setDataSource(srcFile)
val mBitRate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)
if (mBitRate != null && mBitRate.isNotEmpty()) {
val probeBitrate = Integer.valueOf(mBitRate)
bitRate = probeBitrate / 1000 / 100 * 100
}
//1:top left 2:top right 3:bottom left 4:bottom right
val location = 1
val offsetXY = 10
when (waterMarkType) {
TYPE_IMAGE// image
-> {
val photo = PATH + File.separator + "hello.png"
outputPath = PATH + File.separator + "photoMark.mp4"
commandLine = FFmpegUtil.addWaterMarkImg(srcFile, photo, location, bitRate, offsetXY, outputPath)
}
TYPE_GIF// gif
-> {
val gifPath = PATH + File.separator + "ok.gif"
outputPath = PATH + File.separator + "gifWaterMark.mp4"
commandLine = FFmpegUtil.addWaterMarkGif(srcFile, gifPath, location, bitRate, offsetXY, outputPath)
}
TYPE_TEXT// text
-> {
val text = "Hello,FFmpeg"
val textPath = PATH + File.separator + "text.png"
val result = BitmapUtil.textToPicture(textPath, text, Color.BLUE, 20)
outputPath = PATH + File.separator + "textMark.mp4"
commandLine = FFmpegUtil.addWaterMarkImg(srcFile, textPath, location, bitRate, offsetXY, outputPath)
}
else -> {
}
}
}
5 //Remove logo from video, or use to mosaic video
-> {
outputPath = PATH + File.separator + "removeLogo" + suffix
val widthL = 64
val heightL = 40
commandLine = FFmpegUtil.removeLogo(srcFile, 10, 10, widthL, heightL, outputPath)
}
7 //convert video into gif
-> {
outputPath = PATH + File.separator + "video2Gif.gif"
val gifStart = 10
val gifDuration = 3
val width = 320
val frameRate = 10
if (convertGifWithFFmpeg) {
val palettePath = "$PATH/palette.png"
FileUtil.deleteFile(palettePath)
val paletteCmd = FFmpegUtil.generatePalette(srcFile, gifStart, gifDuration,
frameRate, width, palettePath)
val gifCmd = FFmpegUtil.generateGifByPalette(srcFile, palettePath, gifStart, gifDuration,
frameRate, width, outputPath)
val cmdList = ArrayList<Array<String>>()
cmdList.add(paletteCmd)
cmdList.add(gifCmd)
ffmpegHandler!!.executeFFmpegCmds(cmdList)
} else {
convertGifInHighQuality(outputPath!!, srcFile, gifStart, gifDuration, frameRate)
}
}
8 //combine video which layout could be horizontal of vertical
-> {
val input1 = PATH + File.separator + "input1.mp4"
val input2 = PATH + File.separator + "input2.mp4"
outputPath = PATH + File.separator + "multi.mp4"
if (!FileUtil.checkFileExist(input1) || !FileUtil.checkFileExist(input2)) {
return
}
commandLine = FFmpegUtil.multiVideo(input1, input2, outputPath, VideoLayout.LAYOUT_HORIZONTAL)
}
9 //video reverse
-> {
outputPath = PATH + File.separator + "reverse.mp4"
commandLine = FFmpegUtil.reverseVideo(srcFile, outputPath)
}
10 //noise reduction of video
-> {
outputPath = PATH + File.separator + "denoise.mp4"
commandLine = FFmpegUtil.denoiseVideo(srcFile, outputPath)
}
11 //convert video to picture
-> {
outputPath = PATH + File.separator + "Video2Image/"
val imageFile = File(outputPath)
if (!imageFile.exists()) {
if (!imageFile.mkdir()) {
return
}
}
val mStartTime = 10//start time
val mDuration = 5//duration
val mFrameRate = 10//frameRate
commandLine = FFmpegUtil.videoToImage(srcFile, mStartTime, mDuration, mFrameRate, outputPath)
}
12 //combine into picture-in-picture video
-> {
val inputFile1 = PATH + File.separator + "beyond.mp4"
val inputFile2 = PATH + File.separator + "small_girl.mp4"
if (!FileUtil.checkFileExist(inputFile1) && !FileUtil.checkFileExist(inputFile2)) {
return
}
//x and y coordinate points need to be calculated according to the size of full video and small video
//For example: full video is 320x240, small video is 120x90, so x=200 y=150
val x = 200
val y = 150
outputPath = PATH + File.separator + "PicInPic.mp4"
commandLine = FFmpegUtil.picInPicVideo(inputFile1, inputFile2, x, y, outputPath)
}
13 //playing speed of video
-> {
outputPath = PATH + File.separator + "speed.mp4"
commandLine = FFmpegUtil.changeSpeed(srcFile, outputPath, 2f, false)
}
14 // insert thumbnail into video
-> {
val thumbnailPath = PATH + File.separator + "thumb.jpg"
outputPath = PATH + File.separator + "thumbnailVideo" + suffix
commandLine = FFmpegUtil.insertPicIntoVideo(srcFile, thumbnailPath, outputPath)
}
15 //add subtitle into video
-> {
val subtitlePath = PATH + File.separator + "test.ass"
outputPath = PATH + File.separator + "subtitle.mkv"
commandLine = FFmpegUtil.addSubtitleIntoVideo(srcFile, subtitlePath, outputPath)
}
16 // set the rotate degree of video
-> {
val rotateDegree = 90
outputPath = PATH + File.separator + "rotate" + rotateDegree + suffix
commandLine = FFmpegUtil.rotateVideo(srcFile, rotateDegree, outputPath)
}
17 // change video from RGB to gray
-> {
outputPath = PATH + File.separator + "gray" + suffix
commandLine = FFmpegUtil.toGrayVideo(srcFile, outputPath)
}
18 // zoom photo to video
-> {
outputPath = PATH + File.separator + "zoom.mp4"
commandLine = FFmpegUtil.photoZoomToVideo(srcFile, outputPath)
}
else -> {
}
}
if (ffmpegHandler != null && commandLine != null) {
ffmpegHandler!!.executeFFmpegCmd(commandLine)
}
}
/**
* concat/joint two videos together
* It's recommended to convert to the same resolution and encoding
* @param selectedPath the path which is selected
*/
private fun concatVideo(selectedPath: String) {
if (ffmpegHandler == null || selectedPath.isEmpty()) {
return
}
isJointing = true
val targetPath = PATH + File.separator + "jointVideo.mp4"
val transformCmd1 = FFmpegUtil.transformVideoWithEncode(selectedPath, outputPath1)
var width = 0
var height = 0
//probe width and height of the selected video
val probeResult = FFmpegCmd.executeProbeSynchronize(FFmpegUtil.probeFormat(selectedPath))
val mediaBean = JsonParseTool.parseMediaFormat(probeResult)
if (mediaBean?.videoBean != null) {
width = mediaBean.videoBean!!.width
height = mediaBean.videoBean!!.height
Log.e(TAG, "width=$width--height=$height")
}
val transformCmd2 = FFmpegUtil.transformVideoWithEncode(appendPath, width, height, outputPath2)
val fileList = ArrayList<String>()
fileList.add(outputPath1)
fileList.add(outputPath2)
FileUtil.createListFile(listPath, fileList)
val jointVideoCmd = FFmpegUtil.jointVideo(listPath, targetPath)
val commandList = ArrayList<Array<String>>()
commandList.add(transformCmd1)
commandList.add(transformCmd2)
commandList.add(jointVideoCmd)
ffmpegHandler!!.executeFFmpegCmds(commandList)
}
private fun moov(srcFile: String) {
if (!srcFile.endsWith(FileUtil.TYPE_MP4)) {
showToast(getString(R.string.tip_not_mp4_video))
return
}
val filePath = FileUtil.getFilePath(srcFile)
var fileName = FileUtil.getFileName(srcFile)
fileName = "moov_" + fileName!!
outputPath = filePath + File.separator + fileName
val commandLine = FFmpegUtil.moveMoovAhead(srcFile, outputPath)
ffmpegHandler!!.executeFFmpegCmd(commandLine)
}
/**
* Combine pictures into video
*/
private fun handlePhoto() {
// The path of pictures, naming format: img+number.jpg
val picturePath = "$PATH/img/"
if (!FileUtil.checkFileExist(picturePath)) {
return
}
val tempPath = "$PATH/temp/"
FileUtil.deleteFolder(tempPath)
val photoFile = File(picturePath)
val files = photoFile.listFiles()
val cmdList = ArrayList<Array<String>>()
//the resolution of photo which you want to convert
val resolution = "640x320"
for (file in files) {
val inputPath = file.absolutePath
val outputPath = tempPath + file.name
val convertCmd = FFmpegUtil.convertResolution(inputPath, resolution, outputPath)
cmdList.add(convertCmd)
}
val combineVideo = PATH + File.separator + "combineVideo.mp4"
val frameRate = 2// suggested synthetic frameRate:1-10
val commandLine = FFmpegUtil.pictureToVideo(tempPath, frameRate, combineVideo)
cmdList.add(commandLine)
if (ffmpegHandler != null) {
ffmpegHandler!!.executeFFmpegCmds(cmdList)
}
}
private fun convertGifInHighQuality(gifPath: String, videoPath: String, startTime: Int, duration: Int, frameRate: Int) {
Thread {
mHandler.sendEmptyMessage(MSG_BEGIN)
var width = 0
var height = 0
var rotateDegree = 0
try {
val retriever = MediaMetadataRetriever()
retriever.setDataSource(videoPath)
val mWidth = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
val mHeight = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
width = Integer.valueOf(mWidth)
height = Integer.valueOf(mHeight)
val rotate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
rotateDegree = Integer.valueOf(rotate)
retriever.release()
Log.e(TAG, "retrieve width=$width--height=$height--rotate=$rotate")
} catch (e: Exception) {
Log.e(TAG, "retrieve error=$e")
}
val start = System.currentTimeMillis()
val highQualityGif = HighQualityGif(width, height, rotateDegree)
val result = highQualityGif.convertGIF(gifPath, videoPath, startTime, duration, frameRate)
Log.e(TAG, "convert gif result=" + result + "--time=" + (System.currentTimeMillis() - start))
mHandler.sendEmptyMessage(MSG_FINISH)
}.start()
}
override fun onDestroy() {
super.onDestroy()
mHandler.removeCallbacksAndMessages(null)
}
companion object {
private val TAG = VideoHandleActivity::class.java.simpleName
private val PATH = Environment.getExternalStorageDirectory().path
private var outputPath :String ?= null
private const val TYPE_IMAGE = 1
private const val TYPE_GIF = 2
private const val TYPE_TEXT = 3
private const val waterMarkType = TYPE_IMAGE
private const val convertGifWithFFmpeg = false
}
}