Remove FFmpeg and FFprobe

This commit is contained in:
Luna712 2023-09-30 17:46:37 -06:00 committed by GitHub
parent 5ebe79616f
commit 1e0a852c7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -6,9 +6,6 @@ import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import java.io.File import java.io.File
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.ReturnCode
internal interface ThumbnailSpriteCallback { internal interface ThumbnailSpriteCallback {
fun onThumbnailSpriteGenerated(spriteBitmap: Bitmap) fun onThumbnailSpriteGenerated(spriteBitmap: Bitmap)
@ -53,22 +50,13 @@ internal class ThumbnailSpriteGenerator(
val thumbnailWidth = videoWidth / maxColumns val thumbnailWidth = videoWidth / maxColumns
val thumbnailHeight = videoHeight / maxLines val thumbnailHeight = videoHeight / maxLines
val thumbnailList: ArrayList<Bitmap> val thumbnailList = ArrayList<Bitmap>()
if (videoPath.contains(".m3u8")) {
// Using FFmpeg is only really needed for files like M3u8 files
// MediaMetadataRetriever can handle most other things
// If we don't want to support them, then this could really probably be removed
thumbnailList = generateThumbnailsFFmpeg(thumbnailWidth, thumbnailHeight, frameIntervalMillis)
} else {
thumbnailList = ArrayList<Bitmap>()
for (timeInMillis in 0 until videoDuration step frameIntervalMillis) { for (timeInMillis in 0 until videoDuration step frameIntervalMillis) {
val thumbnail = generateThumbnail(timeInMillis, thumbnailWidth, thumbnailHeight) val thumbnail = generateThumbnail(timeInMillis, thumbnailWidth, thumbnailHeight)
if (thumbnail != null) { if (thumbnail != null) {
thumbnailList.add(thumbnail) thumbnailList.add(thumbnail)
} }
} }
}
try { try {
val spriteBitmap = createSpriteBitmap(thumbnailList) val spriteBitmap = createSpriteBitmap(thumbnailList)
@ -124,72 +112,6 @@ internal class ThumbnailSpriteGenerator(
} }
} }
private fun generateThumbnailsFFmpeg(thumbnailWidth: Int, thumbnailHeight: Int, frameIntervalMillis: Long): ArrayList<Bitmap> {
return try {
// Use FFmpeg to generate thumbnail sprites for online videos
val frameIntervalSeconds = (frameIntervalMillis / 1000.0).toDouble()
val context: Context = MainActivity.instance.applicationContext
val cacheDir = context.cacheDir
val outputFilePathPattern = File(cacheDir, "thumbnail%d.jpg").absolutePath
val ffmpegCommand: Array<String> = arrayOf(
"-i", videoPath,
"fps=1/$frameIntervalSeconds,scale=$thumbnailWidth:$thumbnailHeight",
"-preset", "ultrafast",
"-an",
"-y",
outputFilePathPattern
)
val session = FFmpegKit.executeWithArguments(ffmpegCommand)
val returnCode: ReturnCode = session.returnCode
if (ReturnCode.isSuccess(returnCode)) {
// Read the image into a Bitmap
// Determine the number of generated frames dynamically
var frameNumber = 1
val generatedThumbnails = ArrayList<Bitmap>()
while (true) {
val filePath = outputFilePathPattern.replace("%d", frameNumber.toString())
val thumbnail: Bitmap?
if (File(filePath).exists()) {
thumbnail = BitmapFactory.decodeFile(filePath)
// Clean up: Delete the temporary file
File(filePath).delete()
} else {
thumbnail = null
}
if (thumbnail != null) {
generatedThumbnails.add(thumbnail)
val spriteBitmap = createSpriteBitmap(generatedThumbnails)
callback.onThumbnailSpriteGenerated(spriteBitmap)
frameNumber++
} else {
// No more frames found, exit the loop
break
}
}
generatedThumbnails
} else {
val errorMessage = "FFmpeg execution failed with return code: $returnCode, ${session.getOutput()}"
callback.onThumbnailSpriteGenerationError(Exception(errorMessage))
ArrayList<Bitmap>()
}
} catch (e: Exception) {
e.printStackTrace()
callback.onThumbnailSpriteGenerationError(e)
ArrayList<Bitmap>()
}
}
private fun createSpriteBitmap(thumbnails: List<Bitmap>): Bitmap { private fun createSpriteBitmap(thumbnails: List<Bitmap>): Bitmap {
val spriteWidth = thumbnails[0].width * maxColumns val spriteWidth = thumbnails[0].width * maxColumns
val spriteHeight = thumbnails[0].height * maxLines val spriteHeight = thumbnails[0].height * maxLines
@ -207,13 +129,9 @@ internal class ThumbnailSpriteGenerator(
private fun getVideoDuration(): Long { private fun getVideoDuration(): Long {
return try { return try {
if (videoPath.contains(".m3u8")) {
getVideoDurationFFprobe()
} else {
val durationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) val durationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
durationString?.toLong() ?: 0 durationString?.toLong() ?: 0
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
callback.onThumbnailSpriteGenerationError(e) callback.onThumbnailSpriteGenerationError(e)
@ -221,25 +139,8 @@ internal class ThumbnailSpriteGenerator(
} }
} }
private fun getVideoDurationFFprobe(): Long {
val session = FFprobeKit.getMediaInformation(videoPath)
val mediaInformation = session.mediaInformation
val returnCode: ReturnCode = session.returnCode
return if (ReturnCode.isSuccess(returnCode) && mediaInformation.duration != null) {
(mediaInformation.duration.toDouble() * 1000.0).toLong()
} else {
val errorMessage = "FFprobe execution failed with return code: $returnCode, ${session.getOutput()}"
callback.onThumbnailSpriteGenerationError(Exception(errorMessage))
0
}
}
private fun getVideoDimensions(): Pair<Int, Int>? { private fun getVideoDimensions(): Pair<Int, Int>? {
return try { return try {
if (videoPath.contains(".m3u8")) {
getVideoDimensionsFFprobe()
} else {
val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt()
val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt()
@ -248,37 +149,10 @@ internal class ThumbnailSpriteGenerator(
} else { } else {
null null
} }
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
callback.onThumbnailSpriteGenerationError(e) callback.onThumbnailSpriteGenerationError(e)
null null
} }
} }
private fun getVideoDimensionsFFprobe(): Pair<Int, Int>? {
val session = FFprobeKit.getMediaInformation(videoPath)
val mediaInformation = session.mediaInformation
val returnCode: ReturnCode = session.returnCode
return if (ReturnCode.isSuccess(returnCode) && mediaInformation.streams.isNotEmpty()) {
val videoStream = mediaInformation.streams.find { it.width != null && it.height != null }
if (videoStream != null) {
val width = videoStream.width
val height = videoStream.height
if (width != null && height != null) {
Pair(width.toInt(), height.toInt())
} else {
null
}
} else {
null
}
} else {
val errorMessage = "FFprobe execution failed with return code: $returnCode, ${session.getOutput()}"
callback.onThumbnailSpriteGenerationError(Exception(errorMessage))
null
}
}
} }