Finish stabilization
This commit is contained in:
parent
83755fdc93
commit
b815fc2424
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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?
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue