Skip to content

Commit

Permalink
🎉 完成ffmpeg的drawtext和subtitles使用
Browse files Browse the repository at this point in the history
  • Loading branch information
AnJoiner committed Nov 10, 2023
1 parent 782a69f commit 0d436cd
Show file tree
Hide file tree
Showing 21 changed files with 1,285 additions and 41 deletions.
2 changes: 1 addition & 1 deletion README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
如果访问不了全部信息,请跳转[【国内镜像】](https://gitee.com/anjoiner/FFmpegCommand)

## 交叉编译
* Macos 13.2 + GCC + Cmake + NDK 21
* Macos 13.2 + Clang + Cmake + NDK 21

| 第三方库 | 版本 | 下载地址 |
|------------|--------------------|------------------------------------------------------------------------------------------------------|
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ In our development, audio and video related content is often used, generally we
If you can’t access all the information, please go to[【国内镜像】](https://gitee.com/anjoiner/FFmpegCommand)

## Cross Compile
* Macos 13.2 + GCC + Cmake + NDK 21 (支持MediaCodec 编解码)
* Macos 13.2 + Clang + Cmake + NDK 21 (支持MediaCodec 编解码)

| 第三方库 | 版本 | 下载地址 |
|------------|--------------------|------------------------------------------------------------------------------------------------------|
Expand Down
6 changes: 5 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ android {
compileSdkVersion 31
defaultConfig {
applicationId "com.coder.ffmpegtest"
minSdkVersion 15
minSdkVersion 21
targetSdkVersion 31
versionCode 4
versionName "1.2.3"
Expand Down Expand Up @@ -51,6 +51,10 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

buildFeatures{
viewBinding true
}
}

dependencies {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@

<activity android:name=".ui.TestCodecActivity"
android:screenOrientation="portrait"/>
<!--添加文字、字幕-->
<activity android:name=".ui.KFFmpegFontActivity"
android:screenOrientation="portrait" />

<service android:name=".service.FFmpegCommandService" />
<service android:name=".service.FFmpegCommandService2" android:process=":ffmpegCommand2" />
Expand Down
Binary file added app/src/main/assets/fonts/PingFang Bold.ttf
Binary file not shown.
Binary file added app/src/main/assets/fonts/PingFang ExtraLight.ttf
Binary file not shown.
Binary file added app/src/main/assets/fonts/PingFang Heavy.ttf
Binary file not shown.
Binary file added app/src/main/assets/fonts/PingFang Light.ttf
Binary file not shown.
Binary file added app/src/main/assets/fonts/PingFang Medium.ttf
Binary file not shown.
Binary file added app/src/main/assets/fonts/PingFang Regular.ttf
Binary file not shown.
Binary file added app/src/main/assets/mm.mp4
Binary file not shown.
12 changes: 12 additions & 0 deletions app/src/main/assets/srt/mm.srt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
1
00:00:00,200 --> 00:00:01,220
自己不能待在这里

2
00:00:01,460 --> 00:00:02,540
得找个远离闹市

3
00:00:02,720 --> 00:00:03,920
但交通方便的地区

252 changes: 252 additions & 0 deletions app/src/main/java/com/coder/ffmpegtest/ui/KFFmpegFontActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package com.coder.ffmpegtest.ui

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import com.coder.ffmpeg.call.CommonCallBack
import com.coder.ffmpeg.jni.FFmpegCommand
import com.coder.ffmpeg.jni.FFmpegConfig
import com.coder.ffmpeg.utils.CommandParams
import com.coder.ffmpegtest.R
import com.coder.ffmpegtest.databinding.ActivityFfmpegFontBinding
import com.coder.ffmpegtest.model.CommandBean
import com.coder.ffmpegtest.ui.adapter.FFmpegCommandAdapter
import com.coder.ffmpegtest.ui.dialog.PromptDialog
import com.coder.ffmpegtest.utils.FileIOUtils
import com.coder.ffmpegtest.utils.FileUtils
import com.coder.ffmpegtest.utils.ToastUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.io.File
import java.util.ArrayList
import java.util.concurrent.ThreadLocalRandom

/**
* 文字、字幕
*/
class KFFmpegFontActivity : AppCompatActivity() {
private lateinit var binding: ActivityFfmpegFontBinding

private lateinit var videoPath: String
private lateinit var srtPath: String
private lateinit var adapter: FFmpegCommandAdapter
private lateinit var fontDir: File

private val fonts = arrayListOf( // 设置字体
"PingFang ExtraLight.ttf",
"PingFang Light.ttf",
"PingFang Regular.ttf",
"PingFang Medium.ttf",
"PingFang Bold.ttf",
"PingFang Heavy.ttf"
)

// 存储在本地字体
private val localFonts = mutableListOf<File>()

private var mErrorDialog: PromptDialog? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 通过binding绑定视图
binding = ActivityFfmpegFontBinding.inflate(layoutInflater)
setContentView(binding.root)
init() // 初始化
}

private fun init() {
initUI()
initData()
initClick()
}

/**
* 初始化UI
*/
private fun initUI() {
if (mErrorDialog == null) {
mErrorDialog = PromptDialog.newInstance("进度", "完成", "", "停止")
mErrorDialog?.setHasNegativeButton(false)
mErrorDialog?.setOnPromptListener(object : PromptDialog.OnPromptListener {
override fun onPrompt(isPositive: Boolean) {
if (isPositive) FFmpegCommand.cancel()
}
})
}
}

private fun initData() {
if (FileUtils.copy2Memory(this, "mm.mp4")) {
videoPath = File(externalCacheDir, "mm.mp4").absolutePath
}
initList()
initFonts()
initSrt()
}

private fun initList() {
val commands = this.resources.getStringArray(R.array.fonts)
val beans: MutableList<CommandBean> = ArrayList()
for (i in commands.indices) {
beans.add(CommandBean(commands[i], i))
}
binding.list.layoutManager = GridLayoutManager(this, 2)
adapter = FFmpegCommandAdapter(beans)
binding.list.adapter = adapter
}

/**
* 初始化字体
*/
private fun initFonts() {
fontDir = File(externalCacheDir, "fonts")
if (!fontDir.exists()) {
fontDir.mkdirs()
}
// 将字体循环写入本地
for (font in fonts) {
val file = File(fontDir, font)
FileIOUtils.writeFileFromIS(file, assets.open("fonts/$font"))
localFonts.add(file)
}
}

/**
* 初始化字幕文件
*/
private fun initSrt() {
val srtDir = File(externalCacheDir, "srt")
if (!srtDir.exists()) {
srtDir.mkdirs()
}
val file = File(srtDir, "mm.srt")
FileIOUtils.writeFileFromIS(file, assets.open("srt/mm.srt"))
srtPath = file.absolutePath
}

private fun initClick() {
adapter.setItemClickListener(object : FFmpegCommandAdapter.ItemClickListener {
override fun itemClick(id: Int) {
when (id) {
0 -> { // 绘制文本
drawText()
}

1 -> { // 添加字幕
subtitles()
}
}
}
})
}

/**
* 绘制文本
*/
private fun drawText() {
// 随机一个字体路径
val index = ThreadLocalRandom.current().nextInt(localFonts.size)
val outputPath = File(externalCacheDir, "target-drawtext.mp4").absolutePath
val commands = CommandParams()
.append("-c:v")
.append("h264_mediacodec")
.append("-i")
.append(videoPath)
.append("-b") // 硬编码一般需要设置视频的比特率(bitrate)
.append("1500k")
.append("-vf")
.append("drawtext=fontfile=${localFonts[index]}:text=FFmpegCommand:x=(w-text_w)/2:y=(h-text_h)/2:fontsize=100:fontcolor=white")
.append("-c:v")
.append("h264_mediacodec")
.append(outputPath)
.get()

MainScope().launch(Dispatchers.IO) {
FFmpegCommand.runCmd(commands, callback("绘制文本完成", outputPath))
}
}

/**
* 绘制文本
*/
private fun subtitles() {
val mapping = HashMap<String, String>()
mapping["PingFang-SC"] = "PingFang-SC"
FFmpegConfig.setFontDir(this, fontDir.absolutePath, mapping)

val outputPath = File(externalCacheDir, "target-subtitles.mp4").absolutePath
val commands = CommandParams()
.append("-c:v")
.append("h264_mediacodec")
.append("-i")
.append(videoPath)
.append("-b") // 硬编码一般需要设置视频的比特率(bitrate)
.append("1500k")
.append("-vf")
.append("subtitles=${srtPath}:force_style='fontname=苹方 常规,fontsize=40,PrimaryColour=&H000000,OutlineColour=&HF0F0F0,Italic=0,BorderStyle=1,Alignment=2'")
.append("-c:v")
.append("h264_mediacodec")
.append(outputPath)
.get()

MainScope().launch(Dispatchers.IO) {
FFmpegCommand.runCmd(commands, callback("绘制文本完成", outputPath))
}
}


private fun callback(msg: String, targetPath: String?): CommonCallBack? {
return object : CommonCallBack() {
override fun onStart() {
Log.d("FFmpegCmd", "onStart")
runOnUiThread {
mErrorDialog?.show(supportFragmentManager, "Dialog")
}
}

override fun onComplete() {
Log.d("FFmpegCmd", "onComplete")
runOnUiThread {
ToastUtils.show(msg)
mErrorDialog?.setContent(0)
mErrorDialog?.dismissAllowingStateLoss()
binding.contentText.text = targetPath
}

}

override fun onCancel() {
runOnUiThread {
ToastUtils.show("用户取消")
mErrorDialog?.setContent(0)
}
Log.d("FFmpegCmd", "Cancel")
}

override fun onProgress(progress: Int, pts: Long) {
Log.d("FFmpegCmd", progress.toString())
runOnUiThread { mErrorDialog?.setContent(progress) }
}

override fun onError(errorCode: Int, errorMsg: String?) {
Log.d("FFmpegCmd", errorMsg ?: "")
runOnUiThread {
ToastUtils.show(errorMsg)
mErrorDialog?.setContent(0)
mErrorDialog?.dismissAllowingStateLoss()
}
}
}
}

companion object {
fun start(context: Context) {
val intent = Intent(context, KFFmpegFontActivity::class.java)
context.startActivity(intent)
}
}
}
47 changes: 23 additions & 24 deletions app/src/main/java/com/coder/ffmpegtest/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,43 @@ import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.coder.ffmpegtest.R
import com.coder.ffmpegtest.databinding.ActivityFfmpegFontBinding
import com.coder.ffmpegtest.databinding.ActivityMainBinding
import com.coder.ffmpegtest.service.FFmpegCommandService

@SuppressLint("NonConstantResourceId")
class MainActivity : AppCompatActivity(), View.OnClickListener {
var mCommandBtn: Button? = null
var mInfoBtn: Button? = null
var mFormatBtn: Button? = null
var mCodecBtn: Button? = null
var mAbiText: TextView? = null
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intiView()
initListener()
// 通过binding绑定视图
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 初始化
initUI()
initClick()
}

private fun intiView() {
mCommandBtn = findViewById(R.id.btn_command)
mInfoBtn = findViewById(R.id.btn_info)
mFormatBtn = findViewById(R.id.btn_format)
mCodecBtn = findViewById(R.id.btn_codec)
mAbiText = findViewById(R.id.tv_abi)
mAbiText?.text = String.format("当前使用cpu-abi:%s", Build.CPU_ABI)
private fun initUI() {
binding.abiText.text = String.format("当前使用cpu-abi:%s", Build.CPU_ABI)
}

private fun initListener() {
mCommandBtn!!.setOnClickListener(this)
mInfoBtn!!.setOnClickListener(this)
mFormatBtn!!.setOnClickListener(this)
mCodecBtn!!.setOnClickListener(this)
private fun initClick() {
binding.commandBtn.setOnClickListener(this)
binding.infoBtn.setOnClickListener(this)
binding.formatBtn.setOnClickListener(this)
binding.codecBtn.setOnClickListener(this)
binding.fontBtn.setOnClickListener(this)
}

override fun onClick(v: View) {
when (v.id) {
R.id.btn_command -> KFFmpegCommandActivity.start(this)
R.id.btn_info -> KFFmpegInfoActivity.start(this)
R.id.btn_format -> KFFmppegFormatActivity.start(this)
R.id.btn_codec -> KFFmpegCodecActivity.start(this)
R.id.command_btn -> KFFmpegCommandActivity.start(this)
R.id.info_btn -> KFFmpegInfoActivity.start(this)
R.id.format_btn -> KFFmppegFormatActivity.start(this)
R.id.codec_btn -> KFFmpegCodecActivity.start(this)
R.id.font_btn -> KFFmpegFontActivity.start(this)
}
}
}
Loading

0 comments on commit 0d436cd

Please sign in to comment.