Finish stabilization

This commit is contained in:
Matthieu 2022-10-23 16:05:02 +02:00
parent 83755fdc93
commit b815fc2424
7 changed files with 96 additions and 20 deletions

View File

@ -37,6 +37,7 @@ import org.pixeldroid.app.postCreation.carousel.CarouselItem
import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity
import org.pixeldroid.app.utils.BaseThemedWithoutBarActivity
import org.pixeldroid.app.utils.convert
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.ffmpegCompliantUri
@ -58,6 +59,7 @@ data class PhotoData(
var imageDescription: String? = null,
var video: Boolean,
var videoEncodeProgress: Int? = null,
var videoEncodeStabilizationFirstPass: Boolean? = null,
)
class PostCreationActivity : BaseThemedWithoutBarActivity() {
@ -92,7 +94,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
// update UI
binding.carousel.addData(
newPhotoData.map {
CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress)
CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress, it.videoEncodeStabilizationFirstPass)
}
)
}
@ -358,7 +360,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(ffmpegCompliantUri(inputUri)).mediaInformation
val totalVideoDuration = mediaInformation?.duration?.toFloatOrNull()
fun secondPass(){
fun secondPass(stabilizeString: String = ""){
val speed = VideoEditActivity.speedChoices[speedIndex]
val mutedString = if(muted || speedIndex != 1) "-an" else null
@ -371,8 +373,10 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
val separator = if(speedIndex != 1 && !crop.notCropped()) "," else ""
val speedString = if(speedIndex != 1) "setpts=PTS/${speed}" else ""
val speedAndCropString: List<String?> = if(speedIndex!= 1 || !crop.notCropped())
listOf("-filter:v", speedString + separator + cropString)
val separatorStabilize = if(stabilizeString == "" || (speedString == "" && cropString == "")) "" else ","
val speedAndCropString: List<String?> = if(speedIndex!= 1 || !crop.notCropped() || stabilizeString.isNotEmpty())
listOf("-filter:v", stabilizeString + separatorStabilize + speedString + separator + cropString)
// Stream copy is not compatible with filter, but when not filtering we can copy the stream without re-encoding
else listOf("-c", "copy")
@ -447,8 +451,64 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
}
fun stabilizationFirstPass(){
//TODO FFmpeg
secondPass()
val shakeResultsFile = File.createTempFile("temp_shake_results", ".trf", cacheDir)
model.trackTempFile(shakeResultsFile)
val shakeResultsFileUri = shakeResultsFile.toUri()
val shakeResultsFileSafeUri = ffmpegCompliantUri(shakeResultsFileUri).removePrefix("file://")
val inputSafeUri: String = ffmpegCompliantUri(inputUri)
// Map chosen "stabilization force" to shakiness, from 3 to 10
val shakiness = (0f..100f).convert(stabilize, 3f..10f).roundToInt()
val analyzeVideoCommandList = listOf(
"-y", "-i", inputSafeUri,
"-vf", "vidstabdetect=shakiness=$shakiness:accuracy=15:result=$shakeResultsFileSafeUri",
"-f", "null", "-"
).toTypedArray()
FFmpegKit.executeWithArgumentsAsync(analyzeVideoCommandList,
{ firstPass ->
if (ReturnCode.isSuccess(firstPass.returnCode)) {
// Map chosen "stabilization force" to shakiness, from 8 to 40
val smoothing = (0f..100f).convert(stabilize, 8f..40f).roundToInt()
val stabilizeVideoCommand =
"vidstabtransform=smoothing=$smoothing:input=${ffmpegCompliantUri(shakeResultsFileUri).removePrefix("file://")}"
secondPass(stabilizeVideoCommand)
} else {
Log.e(
"PostCreationActivityEncoding",
"Video stabilization first pass failed!"
)
}
},
{ log -> Log.d("PostCreationActivityEncoding", log.message) },
{ statistics: Statistics? ->
val timeInMilliseconds: Int? = statistics?.time
timeInMilliseconds?.let {
if (timeInMilliseconds > 0) {
val completePercentage = totalVideoDuration?.let {
val speedupDurationModifier =
VideoEditActivity.speedChoices[speedIndex].toFloat()
val newTotalDuration = (it - (videoStart ?: 0f) - (it - (videoEnd
?: it))) / speedupDurationModifier
timeInMilliseconds / (10 * newTotalDuration)
}
resultHandler.post {
completePercentage?.let {
val rounded: Int = it.roundToInt()
model.setVideoEncodeAtPosition(position, rounded, true)
binding.carousel.updateProgress(rounded, position, false)
}
}
Log.d(TAG, "Stabilization pass: %$completePercentage.")
}
}
})
}
if(stabilize > 0.01f) {

View File

@ -196,8 +196,8 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null
photoData.value = photoData.value?.map { it.copy(uploadId = null, progress = null) }?.toMutableList()
}
fun setVideoEncodeAtPosition(position: Int, progress: Int?) {
photoData.value?.set(position, photoData.value!![position].copy(videoEncodeProgress = progress))
fun setVideoEncodeAtPosition(position: Int, progress: Int?, stabilizationFirstPass: Boolean = false) {
photoData.value?.set(position, photoData.value!![position].copy(videoEncodeProgress = progress, videoEncodeStabilizationFirstPass = stabilizationFirstPass))
photoData.value = photoData.value
}
@ -377,9 +377,11 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null
val videoCrop: RelativeCropPosition = data.getSerializableExtra(VideoEditActivity.VIDEO_CROP) as RelativeCropPosition
val videoStabilize: Float = data.getFloatExtra(VideoEditActivity.VIDEO_STABILIZE, 1f)
val videoStabilize: Float = data.getFloatExtra(VideoEditActivity.VIDEO_STABILIZE, 0f)
videoEncodeStabilizationFirstPass = videoStabilize > 0.01f
videoEncodeProgress = 0
sessionMap[position]?.let { FFmpegKit.cancel(it) }
_uiState.update { currentUiState ->
currentUiState.copy(

View File

@ -6,5 +6,6 @@ data class CarouselItem constructor(
val imageUrl: Uri,
val caption: String? = null,
val video: Boolean,
var encodeProgress: Int?
var encodeProgress: Int?,
var stabilizationFirstPass: Boolean?
)

View File

@ -95,8 +95,9 @@ class ImageCarousel(
if (thisProgress != null) {
binding.encodeInfoCard.visibility = VISIBLE
binding.encodeProgress.visibility = VISIBLE
binding.encodeInfoText.text =
context.getString(R.string.encode_progress).format(thisProgress)
binding.encodeInfoText.text = (if(data?.getOrNull(position)?.stabilizationFirstPass == true){
context.getString(R.string.analyzing_stabilization)
} else context.getString(R.string.encode_progress)).format(thisProgress)
binding.encodeProgress.progress = thisProgress
} else {
binding.encodeInfoCard.visibility = GONE
@ -582,7 +583,9 @@ class ImageCarousel(
binding.encodeProgress.visibility = VISIBLE
binding.encodeInfoCard.visibility = VISIBLE
binding.encodeProgress.progress = progress
binding.encodeInfoText.text = context.getString(R.string.encode_progress).format(progress)
binding.encodeInfoText.text = (if(data?.getOrNull(position)?.stabilizationFirstPass == true){
context.getString(R.string.analyzing_stabilization)
} else context.getString(R.string.encode_progress)).format(progress)
}
}
}

View File

@ -76,7 +76,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() {
binding.stabilisationSaved.isVisible = true
val typedValue = TypedValue()
val color: Int = if (binding.stabilizer.context.theme
.resolveAttribute(R.attr.colorOnPrimaryContainer, typedValue, true)
.resolveAttribute(R.attr.colorSecondary, typedValue, true)
) typedValue.data else Color.TRANSPARENT
binding.stabilizer.drawable.setTint(color)
@ -196,7 +196,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() {
if(!cropRelativeDimensions.notCropped()){
val typedValue = TypedValue()
val color: Int = if (binding.checkMarkCropped.context.theme
.resolveAttribute(R.attr.colorOnPrimaryContainer, typedValue, true)
.resolveAttribute(R.attr.colorSecondary, typedValue, true)
) typedValue.data else Color.TRANSPARENT
binding.cropper.drawable.setTint(color)
@ -279,7 +279,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() {
thumbnail(uri, resultHandler, binding.thumbnail7, it.times(7))
}
resetControls()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -328,7 +328,9 @@ class VideoEditActivity : BaseThemedWithBarActivity() {
val muted = binding.muter.isSelected
val speedUnchanged = speed == 1
return !muted && videoPositions && speedUnchanged && cropRelativeDimensions.notCropped()
val stabilizationUnchanged = stabilization <= 0.01f || stabilization > 100.5f
return !muted && videoPositions && speedUnchanged && cropRelativeDimensions.notCropped() && stabilizationUnchanged
}
private fun showCropInterface(show: Boolean, uri: Uri? = null){

View File

@ -15,6 +15,7 @@ import android.view.WindowManager
import android.webkit.MimeTypeMap
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.browser.customtabs.CustomTabsIntent
@ -234,6 +235,12 @@ fun Context.themeActionBar(): Int? {
}
}
/** Maps a Float from this range to target range */
fun ClosedRange<Float>.convert(number: Float, target: ClosedRange<Float>): Float {
val ratio = number / (endInclusive - start)
return (ratio * (target.endInclusive - target.start))
}
@ColorInt
fun Context.getColorFromAttr(@AttrRes attrColor: Int): Int = MaterialColors.getColor(this, attrColor, Color.BLACK)

View File

@ -268,6 +268,10 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
<string name="encode_error">Error encoding</string>
<string name="encode_success">Encode success!</string>
<string name="encode_progress">Encode %1$d%%</string>
<string name="stabilize_video">Stabilize video</string>
<string name="stabilize_video_intensity">Change intensity of stabilization</string>
<string name="stabilization_saved">Stabilization saved</string>
<string name="analyzing_stabilization">Analysis for stabilization %1$d%%</string>
<string name="select_video_range">Select what to keep of the video</string>
<string name="mute_video">Mute video</string>
<string name="video_speed">Change video speed</string>
@ -296,9 +300,6 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
<string name="add_images_error">Error while adding images</string>
<string name="notification_thumbnail">"Thumbnail of image in this notification's post"</string>
<string name="post_preview">Preview of a post</string>
<string name="stabilize_video">Stabilize video</string>
<string name="stabilize_video_intensity">Change intensity of stabilization</string>
<string name="stabilization_saved">Stabilization saved</string>
<plurals name="replies_count">
<item quantity="one">%d reply</item>
<item quantity="other">%d replies</item>