diff --git a/.gitignore b/.gitignore
index 603b140..fb73966 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,7 @@
/captures
.externalNativeBuild
.cxx
+.idea/shelf/Uncommitted_changes_before_Checkout_at_2023_11_9,_13_44_\[Changes\]/libffmpeg-org.so
+.idea/shelf/Uncommitted_changes_before_Checkout_at_2023_11_9,_13_44_\[Changes\]/libffmpeg-org1.so
+.idea/shelf/Uncommitted_changes_before_Checkout_at_2023_11_9,_13_44_\[Changes\]/shelved.patch
+.idea/shelf/Uncommitted_changes_before_Checkout_at_2023_11_9__13_44__Changes_.xml
diff --git a/README-CN.md b/README-CN.md
index a773d66..275a150 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -23,7 +23,7 @@
如果访问不了全部信息,请跳转[【国内镜像】](https://gitee.com/anjoiner/FFmpegCommand)
## 交叉编译
-* Macos 13.2 + GCC + Cmake + NDK 21
+* Macos 13.2 + Clang + Cmake + NDK 21
| 第三方库 | 版本 | 下载地址 |
|------------|--------------------|------------------------------------------------------------------------------------------------------|
diff --git a/README.md b/README.md
index 83e3f90..010292a 100644
--- a/README.md
+++ b/README.md
@@ -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 编解码)
| 第三方库 | 版本 | 下载地址 |
|------------|--------------------|------------------------------------------------------------------------------------------------------|
diff --git a/app/build.gradle b/app/build.gradle
index 1aa0379..079a61c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -23,7 +23,7 @@ android {
compileSdkVersion 31
defaultConfig {
applicationId "com.coder.ffmpegtest"
- minSdkVersion 15
+ minSdkVersion 21
targetSdkVersion 31
versionCode 4
versionName "1.2.3"
@@ -51,6 +51,10 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
+
+ buildFeatures{
+ viewBinding true
+ }
}
dependencies {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 84c2524..56d4512 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -47,6 +47,9 @@
+
+
diff --git a/app/src/main/assets/fonts/PingFang Bold.ttf b/app/src/main/assets/fonts/PingFang Bold.ttf
new file mode 100755
index 0000000..accaf1f
Binary files /dev/null and b/app/src/main/assets/fonts/PingFang Bold.ttf differ
diff --git a/app/src/main/assets/fonts/PingFang ExtraLight.ttf b/app/src/main/assets/fonts/PingFang ExtraLight.ttf
new file mode 100755
index 0000000..d9b574b
Binary files /dev/null and b/app/src/main/assets/fonts/PingFang ExtraLight.ttf differ
diff --git a/app/src/main/assets/fonts/PingFang Heavy.ttf b/app/src/main/assets/fonts/PingFang Heavy.ttf
new file mode 100755
index 0000000..f4cf788
Binary files /dev/null and b/app/src/main/assets/fonts/PingFang Heavy.ttf differ
diff --git a/app/src/main/assets/fonts/PingFang Light.ttf b/app/src/main/assets/fonts/PingFang Light.ttf
new file mode 100755
index 0000000..a513705
Binary files /dev/null and b/app/src/main/assets/fonts/PingFang Light.ttf differ
diff --git a/app/src/main/assets/fonts/PingFang Medium.ttf b/app/src/main/assets/fonts/PingFang Medium.ttf
new file mode 100755
index 0000000..982661b
Binary files /dev/null and b/app/src/main/assets/fonts/PingFang Medium.ttf differ
diff --git a/app/src/main/assets/fonts/PingFang Regular.ttf b/app/src/main/assets/fonts/PingFang Regular.ttf
new file mode 100755
index 0000000..8790adb
Binary files /dev/null and b/app/src/main/assets/fonts/PingFang Regular.ttf differ
diff --git a/app/src/main/assets/mm.mp4 b/app/src/main/assets/mm.mp4
new file mode 100644
index 0000000..e99c1ba
Binary files /dev/null and b/app/src/main/assets/mm.mp4 differ
diff --git a/app/src/main/assets/srt/mm.srt b/app/src/main/assets/srt/mm.srt
new file mode 100644
index 0000000..709a238
--- /dev/null
+++ b/app/src/main/assets/srt/mm.srt
@@ -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
+但交通方便的地区
+
diff --git a/app/src/main/java/com/coder/ffmpegtest/ui/KFFmpegFontActivity.kt b/app/src/main/java/com/coder/ffmpegtest/ui/KFFmpegFontActivity.kt
new file mode 100644
index 0000000..957eb91
--- /dev/null
+++ b/app/src/main/java/com/coder/ffmpegtest/ui/KFFmpegFontActivity.kt
@@ -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()
+
+ 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 = 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()
+ 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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/coder/ffmpegtest/ui/MainActivity.kt b/app/src/main/java/com/coder/ffmpegtest/ui/MainActivity.kt
index 8b7156e..d0234e9 100644
--- a/app/src/main/java/com/coder/ffmpegtest/ui/MainActivity.kt
+++ b/app/src/main/java/com/coder/ffmpegtest/ui/MainActivity.kt
@@ -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)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/coder/ffmpegtest/utils/FileIOUtils.kt b/app/src/main/java/com/coder/ffmpegtest/utils/FileIOUtils.kt
new file mode 100644
index 0000000..76f5958
--- /dev/null
+++ b/app/src/main/java/com/coder/ffmpegtest/utils/FileIOUtils.kt
@@ -0,0 +1,455 @@
+package com.coder.ffmpegtest.utils
+
+import android.content.Context
+import android.content.res.AssetManager
+import android.util.Log
+import java.io.*
+import java.nio.ByteBuffer
+import java.nio.MappedByteBuffer
+import java.nio.channels.FileChannel
+import java.nio.charset.Charset
+
+
+/**
+ *Created by AnJoiner on 2021/10/2 09:51
+ */
+object FileIOUtils {
+
+ private const val sBufferSize: Int = 524288
+
+ /**
+ * Write file from input stream.
+ *
+ * @param filePath The path of file.
+ * @param is The input stream.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(filePath: String?, inputStream: InputStream?): Boolean {
+ return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, false, null)
+ }
+
+ /**
+ * Write file from input stream.
+ *
+ * @param filePath The path of file.
+ * @param is The input stream.
+ * @param append True to append, false otherwise.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(
+ filePath: String?,
+ inputStream: InputStream?,
+ append: Boolean
+ ): Boolean {
+ return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, append, null)
+ }
+
+ /**
+ * Write file from input stream.
+ *
+ * @param file The file.
+ * @param is The input stream.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(file: File?, inputStream: InputStream?): Boolean {
+ return writeFileFromIS(file, inputStream, false, null)
+ }
+
+ /**
+ * Write file from input stream.
+ *
+ * @param file The file.
+ * @param is The input stream.
+ * @param append True to append, false otherwise.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(
+ file: File?,
+ inputStream: InputStream?,
+ append: Boolean
+ ): Boolean {
+ return writeFileFromIS(file, inputStream, append, null)
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // writeFileFromIS with progress
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////////////
+ // writeFileFromIS with progress
+ ///////////////////////////////////////////////////////////////////////////
+ /**
+ * Write file from input stream.
+ *
+ * @param filePath The path of file.
+ * @param is The input stream.
+ * @param listener The progress update listener.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(
+ filePath: String?,
+ inputStream: InputStream?,
+ listener: OnProgressUpdateListener?
+ ): Boolean {
+ return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, false, listener)
+ }
+
+ /**
+ * Write file from input stream.
+ *
+ * @param filePath The path of file.
+ * @param is The input stream.
+ * @param append True to append, false otherwise.
+ * @param listener The progress update listener.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(
+ filePath: String?,
+ inputStream: InputStream?,
+ append: Boolean,
+ listener: OnProgressUpdateListener?
+ ): Boolean {
+ return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, append, listener)
+ }
+
+ /**
+ * Write file from input stream.
+ *
+ * @param file The file.
+ * @param is The input stream.
+ * @param listener The progress update listener.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(
+ file: File?,
+ inputStream: InputStream?,
+ listener: OnProgressUpdateListener?
+ ): Boolean {
+ return writeFileFromIS(file, inputStream, false, listener)
+ }
+
+ /**
+ * Write file from input stream.
+ *
+ * @param file The file.
+ * @param is The input stream.
+ * @param append True to append, false otherwise.
+ * @param listener The progress update listener.
+ * @return `true`: success
`false`: fail
+ */
+ fun writeFileFromIS(
+ file: File?,
+ inputStream: InputStream?,
+ append: Boolean,
+ listener: OnProgressUpdateListener?
+ ): Boolean {
+ if (inputStream == null || !FileUtils.createOrExistsFile(file)) {
+ Log.e("FileIOUtils", "create file <$file> failed.")
+ return false
+ }
+ var os: OutputStream? = null
+ return try {
+ os = BufferedOutputStream(FileOutputStream(file, append), sBufferSize)
+ if (listener == null) {
+ val data = ByteArray(sBufferSize)
+ var len: Int
+ while (inputStream.read(data).also { len = it } != -1) {
+ os.write(data, 0, len)
+ }
+ } else {
+ val totalSize: Double = inputStream.available().toDouble()
+ var curSize = 0
+ listener.onProgressUpdate(0.0)
+ val data = ByteArray(sBufferSize)
+ var len: Int
+ while (inputStream.read(data).also { len = it } != -1) {
+ os.write(data, 0, len)
+ curSize += len
+ listener.onProgressUpdate(curSize / totalSize)
+ }
+ }
+ true
+ } catch (e: IOException) {
+ e.printStackTrace()
+ false
+ } finally {
+ try {
+ inputStream.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ try {
+ os?.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+
+ /**
+ * Return the string in file.
+ *
+ * @param filePath The path of file.
+ * @return the string in file
+ */
+ fun readFile2String(filePath: String?): String? {
+ return readFile2String(FileUtils.getFileByPath(filePath), null)
+ }
+
+ /**
+ * Return the string in file.
+ *
+ * @param filePath The path of file.
+ * @param charsetName The name of charset.
+ * @return the string in file
+ */
+ fun readFile2String(filePath: String?, charsetName: String?): String? {
+ return readFile2String(FileUtils.getFileByPath(filePath), charsetName)
+ }
+
+ /**
+ * Return the string in file.
+ *
+ * @param file The file.
+ * @return the string in file
+ */
+ fun readFile2String(file: File?): String? {
+ return readFile2String(file, null)
+ }
+
+ /**
+ * Return the string in file.
+ *
+ * @param file The file.
+ * @param charsetName The name of charset.
+ * @return the string in file
+ */
+ fun readFile2String(file: File?, charsetName: String?): String? {
+ val bytes = readFile2BytesByStream(file) ?: return null
+ return if (charsetName.isNullOrEmpty()) {
+ String(bytes)
+ } else {
+ try {
+ String(bytes, Charset.forName(charsetName))
+ } catch (e: UnsupportedEncodingException) {
+ e.printStackTrace()
+ ""
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // readFile2BytesByStream without progress
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////////////
+ // readFile2BytesByStream without progress
+ ///////////////////////////////////////////////////////////////////////////
+ /**
+ * Return the bytes in file by stream.
+ *
+ * @param filePath The path of file.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByStream(filePath: String?): ByteArray? {
+ return readFile2BytesByStream(FileUtils.getFileByPath(filePath), null)
+ }
+
+ /**
+ * Return the bytes in file by stream.
+ *
+ * @param file The file.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByStream(file: File?): ByteArray? {
+ return readFile2BytesByStream(file, null)
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // readFile2BytesByStream with progress
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////////////
+ // readFile2BytesByStream with progress
+ ///////////////////////////////////////////////////////////////////////////
+ /**
+ * Return the bytes in file by stream.
+ *
+ * @param filePath The path of file.
+ * @param listener The progress update listener.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByStream(
+ filePath: String?,
+ listener: OnProgressUpdateListener?
+ ): ByteArray? {
+ return readFile2BytesByStream(FileUtils.getFileByPath(filePath), listener)
+ }
+
+ /**
+ * Return the bytes in file by stream.
+ *
+ * @param file The file.
+ * @param listener The progress update listener.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByStream(
+ file: File?,
+ listener: OnProgressUpdateListener?
+ ): ByteArray? {
+ return if (file == null || !file.exists()) null else try {
+ var os: ByteArrayOutputStream? = null
+ val `is`: InputStream = BufferedInputStream(FileInputStream(file), sBufferSize)
+ try {
+ os = ByteArrayOutputStream()
+ val b = ByteArray(sBufferSize)
+ var len: Int
+ if (listener == null) {
+ while (`is`.read(b, 0, sBufferSize).also { len = it } != -1) {
+ os.write(b, 0, len)
+ }
+ } else {
+ val totalSize = `is`.available().toDouble()
+ var curSize = 0
+ listener.onProgressUpdate(0.0)
+ while (`is`.read(b, 0, sBufferSize).also { len = it } != -1) {
+ os.write(b, 0, len)
+ curSize += len
+ listener.onProgressUpdate(curSize / totalSize)
+ }
+ }
+ os.toByteArray()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ null
+ } finally {
+ try {
+ `is`.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ try {
+ os?.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ } catch (e: FileNotFoundException) {
+ e.printStackTrace()
+ null
+ }
+ }
+
+ /**
+ * Return the bytes in file by channel.
+ *
+ * @param filePath The path of file.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByChannel(filePath: String?): ByteArray? {
+ return readFile2BytesByChannel(FileUtils.getFileByPath(filePath))
+ }
+
+ /**
+ * Return the bytes in file by channel.
+ *
+ * @param file The file.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByChannel(file: File?): ByteArray? {
+ if (file == null || !file.exists()) return null
+ var fc: FileChannel? = null
+ return try {
+ fc = RandomAccessFile(file, "r").channel
+ if (fc == null) {
+ Log.e("FileIOUtils", "fc is null.")
+ return ByteArray(0)
+ }
+ val byteBuffer: ByteBuffer = ByteBuffer.allocate(fc.size().toInt())
+ while (true) {
+ if (fc.read(byteBuffer) <= 0) break
+ }
+ byteBuffer.array()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ null
+ } finally {
+ try {
+ fc?.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+
+ /**
+ * Return the bytes in file by map.
+ *
+ * @param filePath The path of file.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByMap(filePath: String?): ByteArray? {
+ return readFile2BytesByMap(FileUtils.getFileByPath(filePath))
+ }
+
+
+ /**
+ * Return the bytes in file by map.
+ *
+ * @param file The file.
+ * @return the bytes in file
+ */
+ fun readFile2BytesByMap(file: File?): ByteArray? {
+ if (file == null || !file.exists()) return null
+ var fc: FileChannel? = null
+ return try {
+ fc = RandomAccessFile(file, "r").channel
+ if (fc == null) {
+ Log.e("FileIOUtils", "fc is null.")
+ return ByteArray(0)
+ }
+ val size = fc.size() as Int
+ val mbb: MappedByteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load()
+ val result = ByteArray(size)
+ mbb.get(result, 0, size)
+ result
+ } catch (e: IOException) {
+ e.printStackTrace()
+ null
+ } finally {
+ try {
+ fc?.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ fun readAssetFile(context: Context, fileName: String?): String {
+ //将json数据变成字符串
+ val stringBuilder = StringBuilder()
+ try {
+ //获取assets资源管理器
+ val assetManager: AssetManager = context.assets
+ //通过管理器打开文件并读取
+ val bf = BufferedReader(
+ InputStreamReader(
+ assetManager.open(fileName!!)
+ )
+ )
+ var line: String?
+ while (bf.readLine().also { line = it } != null) {
+ stringBuilder.append(line)
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ return stringBuilder.toString()
+ }
+
+ interface OnProgressUpdateListener {
+ fun onProgressUpdate(progress: Double)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/coder/ffmpegtest/utils/FileUtils.kt b/app/src/main/java/com/coder/ffmpegtest/utils/FileUtils.kt
index c8144b5..16ee59c 100644
--- a/app/src/main/java/com/coder/ffmpegtest/utils/FileUtils.kt
+++ b/app/src/main/java/com/coder/ffmpegtest/utils/FileUtils.kt
@@ -3,15 +3,484 @@ package com.coder.ffmpegtest.utils
import android.content.Context
import android.util.Log
import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.RandomAccessFile
+import java.net.URL
+import javax.net.ssl.HttpsURLConnection
/**
* @author: AnJoiner
* @datetime: 19-12-20
*/
object FileUtils {
+ const val BYTE = 1
+ const val KB = 1024
+ const val MB = 1048576
+ const val GB = 1073741824
+
+ /**
+ * Return the size.
+ *
+ * @param filePath The path of file.
+ * @return the size
+ */
+ fun getSize(filePath: String?): String? {
+ return getSize(getFileByPath(filePath))
+ }
+
+ /**
+ * Return the size.
+ *
+ * @param file The directory.
+ * @return the size
+ */
+ fun getSize(file: File?): String? {
+ if (file == null) return ""
+ return if (file.isDirectory) {
+ getDirSize(file)
+ } else getFileSize(file)
+ }
+
+ /**
+ * Return the size of directory.
+ *
+ * @param dir The directory.
+ * @return the size of directory
+ */
+ private fun getDirSize(dir: File): String? {
+ val len = getDirLength(dir)
+ return if (len == -1L) "" else byte2FitMemorySize(len)
+ }
+
+ /**
+ * Return the size of file.
+ *
+ * @param file The file.
+ * @return the length of file
+ */
+ private fun getFileSize(file: File): String? {
+ val len = getFileLength(file)!!
+ return if (len == -1L) "" else byte2FitMemorySize(len)
+ }
+
+ /**
+ * Return the length.
+ *
+ * @param filePath The path of file.
+ * @return the length
+ */
+ fun getLength(filePath: String?): Long {
+ return getLength(getFileByPath(filePath))
+ }
+
+ /**
+ * Return the length.
+ *
+ * @param file The file.
+ * @return the length
+ */
+ fun getLength(file: File?): Long {
+ if (file == null) return 0
+ return if (file.isDirectory) {
+ getDirLength(file)
+ } else getFileLength(file)!!
+ }
+
+ /**
+ * Return the length of directory.
+ *
+ * @param dir The directory.
+ * @return the length of directory
+ */
+ private fun getDirLength(dir: File): Long {
+ if (!isDir(dir)) return 0
+ var len: Long = 0
+ val files: Array = dir.listFiles()
+ if (files != null && files.size > 0) {
+ for (file in files) {
+ if (file.isDirectory()) {
+ len += getDirLength(file)
+ } else {
+ len += file.length()
+ }
+ }
+ }
+ return len
+ }
+
+ /**
+ * Return the length of file.
+ *
+ * @param filePath The path of file.
+ * @return the length of file
+ */
+ fun getFileLength(filePath: String): Long? {
+
+ val regex = Regex("[a-zA-z]+://[^\\s]*")
+ val isURL = filePath.matches(regex)
+ if (isURL) {
+ try {
+ val conn: HttpsURLConnection = URL(filePath).openConnection() as HttpsURLConnection
+ conn.setRequestProperty("Accept-Encoding", "identity")
+ conn.connect()
+ return if (conn.responseCode == 200) {
+ conn.contentLength.toLong()
+ } else -1
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ return getFileLength(getFileByPath(filePath))
+ }
+
+ /**
+ * Return the length of file.
+ *
+ * @param file The file.
+ * @return the length of file
+ */
+ fun getFileLength(file: File?): Long? {
+ return if (!isFile(file)) -1 else file?.length()
+ }
+
+ /**
+ * Return whether it is a directory.
+ *
+ * @param file The file.
+ * @return `true`: yes
`false`: no
+ */
+ fun isDir(file: File?): Boolean {
+ return file != null && file.exists() && file.isDirectory
+ }
+
+ /**
+ * Return whether it is a file.
+ *
+ * @param filePath The path of file.
+ * @return `true`: yes
`false`: no
+ */
+ fun isFile(filePath: String?): Boolean {
+ return isFile(getFileByPath(filePath))
+ }
+
+ /**
+ * Return whether it is a file.
+ *
+ * @param file The file.
+ * @return `true`: yes
`false`: no
+ */
+ fun isFile(file: File?): Boolean {
+ return file != null && file.exists() && file.isFile
+ }
+
+ /**
+ * Return the file by path.
+ *
+ * @param filePath The path of file.
+ * @return the file
+ */
+ fun getFileByPath(filePath: String?): File? {
+ return if (filePath.isNullOrEmpty()) null else File(filePath)
+ }
+
+ private fun byte2FitMemorySize(byteSize: Long): String? {
+ return byte2FitMemorySize(byteSize, 3)
+ }
+
+ private fun byte2FitMemorySize(byteSize: Long, precision: Int): String? {
+ require(precision >= 0) { "precision shouldn't be less than zero!" }
+ return when {
+ byteSize < 0 -> {
+ throw IllegalArgumentException("byteSize shouldn't be less than zero!")
+ }
+ byteSize < KB -> {
+ String.format("%." + precision + "fB", byteSize.toDouble())
+ }
+ byteSize < MB -> {
+ java.lang.String.format(
+ "%." + precision + "fKB",
+ byteSize.toDouble() / KB
+ )
+ }
+ byteSize < GB -> {
+ java.lang.String.format(
+ "%." + precision + "fMB",
+ byteSize.toDouble() / MB
+ )
+ }
+ else -> {
+ java.lang.String.format(
+ "%." + precision + "fGB",
+ byteSize.toDouble() / GB
+ )
+ }
+ }
+ }
+
+
+ /**
+ * Delete the directory.
+ *
+ * @param file The file.
+ * @return `true`: success
`false`: fail
+ */
+ fun delete(file: File?): Boolean {
+ if (file == null) return false
+ return if (file.isDirectory) {
+ deleteDir(file)
+ } else deleteFile(file)
+ }
+
+ /**
+ * Delete the file.
+ *
+ * @param file The file.
+ * @return `true`: success
`false`: fail
+ */
+ private fun deleteFile(file: File?): Boolean {
+ return file != null && (!file.exists() || file.isFile && file.delete())
+ }
+
+
+ /**
+ * Delete the directory.
+ *
+ * @param dir The directory.
+ * @return `true`: success
`false`: fail
+ */
+ private fun deleteDir(dir: File?): Boolean {
+ if (dir == null) return false
+ // dir doesn't exist then return true
+ if (!dir.exists()) return true
+ // dir isn't a directory then return false
+ if (!dir.isDirectory) return false
+ val files = dir.listFiles()
+ if (files != null && files.size > 0) {
+ for (file in files) {
+ if (file.isFile) {
+ if (!file.delete()) return false
+ } else if (file.isDirectory) {
+ if (!deleteDir(file)) return false
+ }
+ }
+ }
+ return dir.delete()
+ }
+
+ /**
+ * Create a file if it doesn't exist, otherwise do nothing.
+ *
+ * @param filePath The path of file.
+ * @return `true`: exists or creates successfully
`false`: otherwise
+ */
+ fun createOrExistsFile(filePath: String?): Boolean {
+ return createOrExistsFile(getFileByPath(filePath))
+ }
+
+ /**
+ * Create a file if it doesn't exist, otherwise do nothing.
+ *
+ * @param file The file.
+ * @return `true`: exists or creates successfully
`false`: otherwise
+ */
+ fun createOrExistsFile(file: File?): Boolean {
+ if (file == null) return false
+ if (file.exists()) return file.isFile
+ return if (!createOrExistsDir(file.parentFile)) false else try {
+ file.createNewFile()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ false
+ }
+ }
+
+ /**
+ * Create a directory if it doesn't exist, otherwise do nothing.
+ *
+ * @param dirPath The path of directory.
+ * @return `true`: exists or creates successfully
`false`: otherwise
+ */
+ fun createOrExistsDir(dirPath: String?): Boolean {
+ return createOrExistsDir(getFileByPath(dirPath))
+ }
+
+ /**
+ * Create a directory if it doesn't exist, otherwise do nothing.
+ *
+ * @param file The file.
+ * @return `true`: exists or creates successfully
`false`: otherwise
+ */
+ fun createOrExistsDir(file: File?): Boolean {
+ return file != null && if (file.exists()) file.isDirectory else file.mkdirs()
+ }
+
+ /**
+ * Copy the directory or file.
+ *
+ * @param srcPath The path of source.
+ * @param destPath The path of destination.
+ * @return `true`: success
`false`: fail
+ */
+ fun copy(
+ srcPath: String?,
+ destPath: String?
+ ): Boolean {
+ return copy(getFileByPath(srcPath), getFileByPath(destPath), null)
+ }
+
+ /**
+ * Copy the directory or file.
+ *
+ * @param srcPath The path of source.
+ * @param destPath The path of destination.
+ * @param listener The replace listener.
+ * @return `true`: success
`false`: fail
+ */
+ fun copy(
+ srcPath: String?,
+ destPath: String?,
+ listener: OnReplaceListener?
+ ): Boolean {
+ return copy(getFileByPath(srcPath), getFileByPath(destPath), listener)
+ }
+
+ /**
+ * Copy the directory or file.
+ *
+ * @param src The source.
+ * @param dest The destination.
+ * @return `true`: success
`false`: fail
+ */
+ fun copy(
+ src: File?,
+ dest: File?
+ ): Boolean {
+ return copy(src, dest, null)
+ }
+
+ /**
+ * Copy the directory or file.
+ *
+ * @param src The source.
+ * @param dest The destination.
+ * @param listener The replace listener.
+ * @return `true`: success
`false`: fail
+ */
+ fun copy(
+ src: File?,
+ dest: File?,
+ listener: OnReplaceListener?
+ ): Boolean {
+ if (src == null) return false
+ return if (src.isDirectory) {
+ copyDir(src, dest, listener)
+ } else copyFile(src, dest, listener)
+ }
+
+ /**
+ * Copy the directory.
+ *
+ * @param srcDir The source directory.
+ * @param destDir The destination directory.
+ * @param listener The replace listener.
+ * @return `true`: success
`false`: fail
+ */
+ private fun copyDir(
+ srcDir: File,
+ destDir: File?,
+ listener: OnReplaceListener?
+ ): Boolean {
+ return copyOrMoveDir(srcDir, destDir, listener!!, false)
+ }
+
+ /**
+ * Copy the file.
+ *
+ * @param srcFile The source file.
+ * @param destFile The destination file.
+ * @param listener The replace listener.
+ * @return `true`: success
`false`: fail
+ */
+ private fun copyFile(
+ srcFile: File,
+ destFile: File?,
+ listener: OnReplaceListener?
+ ): Boolean {
+ return copyOrMoveFile(srcFile, destFile, listener, false)
+ }
+
+ private fun copyOrMoveDir(
+ srcDir: File?,
+ destDir: File?,
+ listener: OnReplaceListener,
+ isMove: Boolean
+ ): Boolean {
+ if (srcDir == null || destDir == null) return false
+ // destDir's path locate in srcDir's path then return false
+ val srcPath = srcDir.path + File.separator
+ val destPath = destDir.path + File.separator
+ if (destPath.contains(srcPath)) return false
+ if (!srcDir.exists() || !srcDir.isDirectory) return false
+ if (!createOrExistsDir(destDir)) return false
+ val files = srcDir.listFiles()
+ if (files != null && files.size > 0) {
+ for (file in files) {
+ val oneDestFile = File(destPath + file.name)
+ if (file.isFile) {
+ if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false
+ } else if (file.isDirectory) {
+ if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false
+ }
+ }
+ }
+ return !isMove || deleteDir(srcDir)
+ }
+
+ private fun copyOrMoveFile(
+ srcFile: File?,
+ destFile: File?,
+ listener: OnReplaceListener?,
+ isMove: Boolean
+ ): Boolean {
+ if (srcFile == null || destFile == null) return false
+ // srcFile equals destFile then return false
+ if (srcFile == destFile) return false
+ // srcFile doesn't exist or isn't a file then return false
+ if (!srcFile.exists() || !srcFile.isFile) return false
+ if (destFile.exists()) {
+ if (listener == null || listener.onReplace(
+ srcFile,
+ destFile
+ )
+ ) { // require delete the old file
+ if (!destFile.delete()) { // unsuccessfully delete then return false
+ return false
+ }
+ } else {
+ return true
+ }
+ }
+ return if (!createOrExistsDir(destFile.parentFile)) false else try {
+ (FileIOUtils.writeFileFromIS(destFile.absolutePath, FileInputStream(srcFile))
+ && !(isMove && !deleteFile(srcFile)))
+ } catch (e: FileNotFoundException) {
+ e.printStackTrace()
+ false
+ }
+ }
+
+
+ fun getFileNameByUrl(url: String?):String {
+ return url?.substring(url.lastIndexOf('/') + 1)?:""
+ }
+
+ interface OnReplaceListener {
+ fun onReplace(srcFile: File?, destFile: File?): Boolean
+ }
+
/**
* 将asset文件写入缓存
*/
diff --git a/app/src/main/res/layout/activity_ffmpeg_font.xml b/app/src/main/res/layout/activity_ffmpeg_font.xml
new file mode 100644
index 0000000..d663675
--- /dev/null
+++ b/app/src/main/res/layout/activity_ffmpeg_font.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index eca73d5..16f45d4 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -10,64 +10,73 @@
tools:context=".ui.MainActivity">
-
-
-
+
+
+
+
-
+
+
+
生成静音音频
+
+ - 文本绘制(drawtext)
+ - 字幕添加(subtitles)
+
+
\ No newline at end of file
diff --git a/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCmd.kt b/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCmd.kt
index 3c1f1cc..5977d49 100644
--- a/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCmd.kt
+++ b/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCmd.kt
@@ -1,6 +1,5 @@
package com.coder.ffmpeg.jni
-import android.util.Log
import com.coder.ffmpeg.annotation.CodecAttribute
import com.coder.ffmpeg.annotation.CodecProperty
import com.coder.ffmpeg.annotation.FormatAttribute
@@ -39,7 +38,9 @@ internal class FFmpegCmd private constructor() {
/**
* Whether to enable debugging mode
* @param debug true or false
+ * you can see [FFmpegConfig.setDebug]
*/
+ @Deprecated("delete")
external fun setDebug(debug: Boolean)
/**
@@ -101,13 +102,6 @@ internal class FFmpegCmd private constructor() {
return if (ffdebug) cmds else cmd
}
- /**
- * Execute ffmpeg command method
- * @param command ffmeng command
- * @return execute status
- */
- private external fun run(command: String?): Int
-
/**
* Execute ffmpeg command method
* @param command ffmeng command
@@ -131,6 +125,13 @@ internal class FFmpegCmd private constructor() {
* @param type information type.
*/
private external fun info(videoPath: String?, type: Int): Int
+
+ /**
+ * Call native to get media information.
+ * @param videoPath media path
+ * @param type information type.
+ */
+ private external fun codec(videoPath: String?, type: Int): CodecInfo?
/**
* Provide method to get codec info .
* @param property property type.
@@ -138,13 +139,6 @@ internal class FFmpegCmd private constructor() {
fun getCodecProperty(videoPath: String?,@CodecProperty property: Int): CodecInfo? {
return codec(videoPath, property)
}
- /**
- * Call native to get media information.
- * @param videoPath media path
- * @param type information type.
- */
- private external fun codec(videoPath: String?, type: Int): CodecInfo?
-
/**
* Provide method to get format info .
* @param format format type.
diff --git a/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCommand.kt b/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCommand.kt
index daaf431..ec15a69 100644
--- a/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCommand.kt
+++ b/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegCommand.kt
@@ -16,7 +16,9 @@ object FFmpegCommand {
* Whether to enable debug mode
*
* @param debug true:enable false :not enable
+ * you can see [FFmpegConfig.setDebug]
*/
+ @Deprecated("the method is deprecated", ReplaceWith("FFmpegConfig.setDebug(debug)"))
@JvmStatic
fun setDebug(debug: Boolean) {
FFmpegCmd.instance?.setDebug(debug)
diff --git a/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegConfig.kt b/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegConfig.kt
new file mode 100644
index 0000000..0cfcb10
--- /dev/null
+++ b/ffmpeg/src/main/java/com/coder/ffmpeg/jni/FFmpegConfig.kt
@@ -0,0 +1,126 @@
+package com.coder.ffmpeg.jni
+
+import android.content.Context
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.util.Collections
+import java.util.concurrent.atomic.AtomicReference
+
+
+class FFmpegConfig {
+
+ companion object {
+
+ init {
+ System.loadLibrary("ffmpeg-org")
+ System.loadLibrary("ffmpeg-command")
+ }
+
+ /**
+ * Set the env of native
+ * @param name env name
+ * @param value env value
+ */
+ private external fun setNativeEnv(name: String, value: String)
+
+ /**
+ * Whether to enable debugging mode
+ * @param debug true or false
+ */
+ external fun setDebug(debug: Boolean)
+
+ /**
+ * Set font config dir for fontconfig
+ * Note:It's a config dir not font dir
+ * @param configPath the font config dir
+ */
+ fun setFontConfigPath(configPath: String) {
+ setNativeEnv("FONTCONFIG_PATH", configPath)
+ }
+
+ /**
+ * Set font config file for fontconfig
+ * Note:It's a config file not font file
+ * @param configFile the font config file
+ */
+ fun setFontConfigFile(configFile: String) {
+ setNativeEnv("FONTCONFIG_FILE", configFile)
+ }
+
+ /**
+ * Set font dir for fontconfig
+ * @param context context for application
+ * @param fontDir the font dir contain fonts (.ttf and .otf files)
+ * @param fontNameMapping
+ */
+ fun setFontDir(context: Context, fontDir:String, fontNameMapping: Map){
+ setFontDirList(context, Collections.singletonList(fontDir),fontNameMapping)
+ }
+ /**
+ * Set font dir for fontconfig
+ * @param context context for application
+ * @param fontDirList list of directories that contain fonts (.ttf and .otf files)
+ */
+ fun setFontDirList(context: Context, fontDirList: List, fontNameMapping: Map) {
+ var validFontNameMappingCount = 0
+ val cacheDir = context.cacheDir
+ val fontConfigDir = File(cacheDir, "fontconfig")
+ if (!fontConfigDir.exists()) {
+ fontConfigDir.mkdirs()
+ }
+ // Create font config
+ val fontConfigFile = File(fontConfigDir, "fonts.conf")
+ if (fontConfigFile.exists() && fontConfigFile.isFile) {
+ fontConfigFile.delete()
+ }
+ fontConfigFile.createNewFile()
+ val fontNameMappingBlock = StringBuilder()
+ if (fontNameMapping.isNotEmpty()){
+ for (entry in fontNameMapping.entries) {
+ val fontName: String = entry.key
+ val mappedFontName: String = entry.value
+
+ if ((fontName.trim().isNotEmpty()) && (mappedFontName.trim().isNotEmpty())) {
+ fontNameMappingBlock.append(" \n");
+ fontNameMappingBlock.append(" \n");
+ fontNameMappingBlock.append(String.format(" %s\n", fontName));
+ fontNameMappingBlock.append(" \n");
+ fontNameMappingBlock.append(" \n");
+ fontNameMappingBlock.append(String.format(" %s\n", mappedFontName));
+ fontNameMappingBlock.append(" \n");
+ fontNameMappingBlock.append(" \n");
+
+ validFontNameMappingCount++
+ }
+ }
+ }
+ val fontConfigBuilder = StringBuilder()
+ fontConfigBuilder.append("\n")
+ fontConfigBuilder.append("\n")
+ fontConfigBuilder.append("\n")
+ fontConfigBuilder.append(" .\n")
+ for (fontDirectoryPath in fontDirList) {
+ fontConfigBuilder.append(" ")
+ fontConfigBuilder.append(fontDirectoryPath)
+ fontConfigBuilder.append("\n")
+ }
+ fontConfigBuilder.append(fontNameMappingBlock)
+ fontConfigBuilder.append("\n")
+ val reference = AtomicReference()
+ try {
+ val outputStream = FileOutputStream(fontConfigFile)
+ reference.set(outputStream)
+
+ outputStream.write(fontConfigBuilder.toString().toByteArray())
+ outputStream.flush()
+ setFontConfigPath(fontConfigDir.absolutePath)
+ }catch (e:IOException){
+ e.printStackTrace()
+ }finally {
+ reference.get().close()
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-command.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-command.so
index ad83913..f1238a4 100755
Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-command.so and b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-command.so differ
diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-org.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-org.so
index 7973a54..4a38ce2 100755
Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-org.so and b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg-org.so differ
diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-command.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-command.so
index 024bb38..00c145a 100755
Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-command.so and b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-command.so differ
diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-org.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-org.so
index a6ba8c3..46b1000 100755
Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-org.so and b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg-org.so differ