mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Remove FFmpeg and FFprobe
This commit is contained in:
parent
5ebe79616f
commit
1e0a852c7a
1 changed files with 13 additions and 139 deletions
|
@ -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,20 +50,11 @@ 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>()
|
||||||
|
for (timeInMillis in 0 until videoDuration step frameIntervalMillis) {
|
||||||
if (videoPath.contains(".m3u8")) {
|
val thumbnail = generateThumbnail(timeInMillis, thumbnailWidth, thumbnailHeight)
|
||||||
// Using FFmpeg is only really needed for files like M3u8 files
|
if (thumbnail != null) {
|
||||||
// MediaMetadataRetriever can handle most other things
|
thumbnailList.add(thumbnail)
|
||||||
// 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) {
|
|
||||||
val thumbnail = generateThumbnail(timeInMillis, thumbnailWidth, thumbnailHeight)
|
|
||||||
if (thumbnail != null) {
|
|
||||||
thumbnailList.add(thumbnail)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,72 +111,6 @@ internal class ThumbnailSpriteGenerator(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
@ -207,13 +129,9 @@ internal class ThumbnailSpriteGenerator(
|
||||||
|
|
||||||
private fun getVideoDuration(): Long {
|
private fun getVideoDuration(): Long {
|
||||||
return try {
|
return try {
|
||||||
if (videoPath.contains(".m3u8")) {
|
val durationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||||
getVideoDurationFFprobe()
|
|
||||||
} else {
|
|
||||||
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,64 +139,20 @@ 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")) {
|
val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt()
|
||||||
getVideoDimensionsFFprobe()
|
val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt()
|
||||||
} else {
|
|
||||||
val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt()
|
|
||||||
val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt()
|
|
||||||
|
|
||||||
if (width != null && height != null) {
|
if (width != null && height != null) {
|
||||||
Pair(width, height)
|
Pair(width, height)
|
||||||
} 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue