diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt index 4a9c3a9d..772bea34 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -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) } ) } @@ -127,15 +129,17 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { uiState.newEncodingJobPosition?.let { position -> uiState.newEncodingJobMuted?.let { muted -> - uiState.newEncodingJobVideoStart?.let { videoStart -> - uiState.newEncodingJobVideoEnd?.let { videoEnd -> + uiState.newEncodingJobVideoStart.let { videoStart -> + uiState.newEncodingJobVideoEnd.let { videoEnd -> uiState.newEncodingJobSpeedIndex?.let { speedIndex -> uiState.newEncodingJobVideoCrop?.let { crop -> - startEncoding(position, muted, - videoStart, videoEnd, - speedIndex, crop - ) - model.encodingStarted() + uiState.newEncodingJobStabilize?.let { stabilize -> + startEncoding(position, muted, + videoStart, videoEnd, + speedIndex, crop, stabilize, + ) + model.encodingStarted() + } } } } @@ -336,7 +340,8 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { videoStart: Float?, videoEnd: Float?, speedIndex: Int, - crop: VideoEditActivity.RelativeCropPosition + crop: VideoEditActivity.RelativeCropPosition, + stabilize: Float ) { val originalUri = model.getPhotoData().value!![position].imageUri @@ -355,91 +360,165 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(ffmpegCompliantUri(inputUri)).mediaInformation val totalVideoDuration = mediaInformation?.duration?.toFloatOrNull() - val speed = VideoEditActivity.speedChoices[speedIndex] + fun secondPass(stabilizeString: String = ""){ + val speed = VideoEditActivity.speedChoices[speedIndex] - val mutedString = if(muted || speedIndex != 1) "-an" else null - val startString: List = if(videoStart != null) listOf("-ss", "${videoStart/speed.toFloat()}") else listOf(null, null) + val mutedString = if(muted || speedIndex != 1) "-an" else null + val startString: List = if(videoStart != null) listOf("-ss", "${videoStart/speed.toFloat()}") else listOf(null, null) - val endString: List = if(videoEnd != null) listOf("-to", "${videoEnd/speed.toFloat() - (videoStart ?: 0f)/speed.toFloat()}") else listOf(null, null) + val endString: List = if(videoEnd != null) listOf("-to", "${videoEnd/speed.toFloat() - (videoStart ?: 0f)/speed.toFloat()}") else listOf(null, null) - // iw and ih are variables for the original width and height values, FFmpeg will know them - val cropString = if(crop.notCropped()) "" else "crop=${crop.relativeWidth}*iw:${crop.relativeHeight}*ih:${crop.relativeX}*iw:${crop.relativeY}*ih" - val separator = if(speedIndex != 1 && !crop.notCropped()) "," else "" - val speedString = if(speedIndex != 1) "setpts=PTS/${speed}" else "" + // iw and ih are variables for the original width and height values, FFmpeg will know them + val cropString = if(crop.notCropped()) "" else "crop=${crop.relativeWidth}*iw:${crop.relativeHeight}*ih:${crop.relativeX}*iw:${crop.relativeY}*ih" + val separator = if(speedIndex != 1 && !crop.notCropped()) "," else "" + val speedString = if(speedIndex != 1) "setpts=PTS/${speed}" else "" - val speedAndCropString: List = if(speedIndex!= 1 || !crop.notCropped()) - listOf("-filter:v", speedString + separator + cropString) + val separatorStabilize = if(stabilizeString == "" || (speedString == "" && cropString == "")) "" else "," + + val speedAndCropString: List = 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") - // This should be set when re-encoding is required (otherwise it defaults to mpeg which then doesn't play) - val encodePreset: List = if(speedIndex != 1 && !crop.notCropped()) listOf("-c:v", "libx264", "-preset", "ultrafast") else listOf(null, null, null, null) + // This should be set when re-encoding is required (otherwise it defaults to mpeg which then doesn't play) + val encodePreset: List = if(speedIndex != 1 && !crop.notCropped()) listOf("-c:v", "libx264", "-preset", "ultrafast") else listOf(null, null, null, null) - val session: FFmpegSession = - FFmpegKit.executeWithArgumentsAsync(listOfNotNull( - startString[0], startString[1], - "-i", ffmpegCompliantUri, - speedAndCropString[0], speedAndCropString[1], - endString[0], endString[1], - mutedString, "-y", - encodePreset[0], encodePreset[1], encodePreset[2], encodePreset[3], - outputVideoPath, - ).toTypedArray(), - //val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c:v libvpx-vp9 -c:a copy -an -y $outputVideoPath", - { session -> - val returnCode = session.returnCode - if (ReturnCode.isSuccess(returnCode)) { - fun successResult() { - // Hide progress indicator in carousel - binding.carousel.updateProgress(null, position, false) - val (imageSize, _) = outputVideoPath.toUri().let { - model.setUriAtPosition(it, position) - model.getSizeAndVideoValidate(it, position) + val session: FFmpegSession = + FFmpegKit.executeWithArgumentsAsync(listOfNotNull( + startString[0], startString[1], + "-i", ffmpegCompliantUri, + speedAndCropString[0], speedAndCropString[1], + endString[0], endString[1], + mutedString, "-y", + encodePreset[0], encodePreset[1], encodePreset[2], encodePreset[3], + outputVideoPath, + ).toTypedArray(), + //val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c:v libvpx-vp9 -c:a copy -an -y $outputVideoPath", + { session -> + val returnCode = session.returnCode + if (ReturnCode.isSuccess(returnCode)) { + fun successResult() { + // Hide progress indicator in carousel + binding.carousel.updateProgress(null, position, false) + val (imageSize, _) = outputVideoPath.toUri().let { + model.setUriAtPosition(it, position) + model.getSizeAndVideoValidate(it, position) + } + model.setVideoEncodeAtPosition(position, null) + model.setSizeAtPosition(imageSize, position) + } + + val post = resultHandler.post { + successResult() + } + if(!post) { + Log.e(TAG, "Failed to post changes, trying to recover in 100ms") + resultHandler.postDelayed({successResult()}, 100) + } + Log.d(TAG, "Encode completed successfully in ${session.duration} milliseconds") + } else { + resultHandler.post { + binding.carousel.updateProgress(null, position, error = true) + model.setVideoEncodeAtPosition(position, null) + } + Log.e(TAG, "Encode failed with state ${session.state} and rc $returnCode.${session.failStackTrace}") } - model.setVideoEncodeAtPosition(position, null) - model.setSizeAtPosition(imageSize, position) - } + }, + { log -> Log.d("PostCreationActivityEncoding", log.message) } + ) { statistics: Statistics? -> - val post = resultHandler.post { - successResult() - } - if(!post) { - Log.e(TAG, "Failed to post changes, trying to recover in 100ms") - resultHandler.postDelayed({successResult()}, 100) - } - Log.d(TAG, "Encode completed successfully in ${session.duration} milliseconds") - } else { - resultHandler.post { - binding.carousel.updateProgress(null, position, error = true) - model.setVideoEncodeAtPosition(position, null) - } - Log.e(TAG, "Encode failed with state ${session.state} and rc $returnCode.${session.failStackTrace}") - } - }, - { 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 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) - binding.carousel.updateProgress(rounded, position, false) + 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) + binding.carousel.updateProgress(rounded, position, false) + } + } + Log.d(TAG, "Encoding video: %$completePercentage.") } } - Log.d(TAG, "Encoding video: %$completePercentage.") } - } + model.registerNewFFmpegSession(position, session.sessionId) } - model.registerNewFFmpegSession(position, session.sessionId) + + fun stabilizationFirstPass(){ + + 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) { + // Stabilization was requested: we need an additional first pass to get stabilization data + stabilizationFirstPass() + } else { + // Immediately call the second pass, no stabilization needed + secondPass() + } + } private fun edit(position: Int) { diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index 02850774..a701a717 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -63,7 +63,7 @@ data class PostCreationActivityUiState( val newEncodingJobVideoStart: Float? = null, val newEncodingJobVideoEnd: Float? = null, val newEncodingJobVideoCrop: RelativeCropPosition? = null, - + val newEncodingJobStabilize: Float? = null, ) class PostCreationViewModel(application: Application, clipdata: ClipData? = null, val instance: InstanceDatabaseEntity? = null) : AndroidViewModel(application) { @@ -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,7 +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, 0f) + + videoEncodeStabilizationFirstPass = videoStabilize > 0.01f videoEncodeProgress = 0 + sessionMap[position]?.let { FFmpegKit.cancel(it) } _uiState.update { currentUiState -> currentUiState.copy( @@ -386,7 +390,8 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null newEncodingJobSpeedIndex = speedIndex, newEncodingJobVideoStart = videoStart, newEncodingJobVideoEnd = videoEnd, - newEncodingJobVideoCrop = videoCrop + newEncodingJobVideoCrop = videoCrop, + newEncodingJobStabilize = videoStabilize ) } } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt index bbd4b8c7..6adb2b83 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt @@ -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? ) \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt index d67ef4d1..97b9f89c 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt @@ -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) } } } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt index 674c3c70..43060fed 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt @@ -32,6 +32,7 @@ import com.arthenica.ffmpegkit.MediaInformation import com.arthenica.ffmpegkit.ReturnCode import com.bumptech.glide.Glide import com.google.android.material.slider.RangeSlider +import com.google.android.material.slider.Slider import org.pixeldroid.app.R import org.pixeldroid.app.databinding.ActivityVideoEditBinding import org.pixeldroid.app.postCreation.PostCreationActivity @@ -42,7 +43,6 @@ import java.io.File import java.io.Serializable import kotlin.math.absoluteValue - class VideoEditActivity : BaseThemedWithBarActivity() { data class RelativeCropPosition( @@ -68,6 +68,25 @@ class VideoEditActivity : BaseThemedWithBarActivity() { private var cropRelativeDimensions: RelativeCropPosition = RelativeCropPosition() + private var stabilization: Float = 0f + set(value){ + field = value + if(value > 0.01f && value <= 100f){ + // Stabilization requested, show UI + binding.stabilisationSaved.isVisible = true + val typedValue = TypedValue() + val color: Int = if (binding.stabilizer.context.theme + .resolveAttribute(R.attr.colorSecondary, typedValue, true) + ) typedValue.data else Color.TRANSPARENT + + binding.stabilizer.drawable.setTint(color) + } + else { + binding.stabilisationSaved.isVisible = false + binding.stabilizer.drawable.setTintList(null) + } + } + private var speed: Int = 1 set(value) { field = value @@ -177,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) @@ -232,6 +251,21 @@ class VideoEditActivity : BaseThemedWithBarActivity() { }.show() } + binding.stabilizer.setOnClickListener { + AlertDialog.Builder(this).apply { + setIcon(R.drawable.video_stable) + setTitle(R.string.stabilize_video_intensity) + val slider = Slider(context).apply { + valueFrom = 0f + valueTo = 100f + value = stabilization + } + setView(slider) + setNegativeButton(android.R.string.cancel) { _, _ -> } + setPositiveButton(android.R.string.ok) { _, _ -> stabilization = slider.value} + }.show() + } + val thumbInterval: Float? = duration?.div(7) @@ -245,7 +279,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() { thumbnail(uri, resultHandler, binding.thumbnail7, it.times(7)) } - + resetControls() } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -294,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){ @@ -306,9 +342,14 @@ class VideoEditActivity : BaseThemedWithBarActivity() { if(show) binding.cropSavedCard.visibility = View.GONE else if(!cropRelativeDimensions.notCropped()) binding.cropSavedCard.visibility = View.VISIBLE + binding.stabilisationSaved.visibility = + if(!show && stabilization > 0.01f && stabilization <= 100f) View.VISIBLE + else View.GONE + binding.muter.visibility = visibilityOfOthers binding.speeder.visibility = visibilityOfOthers binding.cropper.visibility = visibilityOfOthers + binding.stabilizer.visibility = visibilityOfOthers binding.videoRangeSeekBar.visibility = visibilityOfOthers binding.videoView.visibility = visibilityOfOthers binding.thumbnail1.visibility = visibilityOfOthers @@ -327,6 +368,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() { } private fun returnWithValues() { + //TODO Check if some of these should be null to indicate no changes in that category? Ex start/end val intent = Intent(this, PostCreationActivity::class.java) .apply { putExtra(PhotoEditActivity.PICTURE_POSITION, videoPosition) @@ -336,6 +378,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() { putExtra(VIDEO_START, binding.videoRangeSeekBar.values.first()) putExtra(VIDEO_END, binding.videoRangeSeekBar.values[2]) putExtra(VIDEO_CROP, cropRelativeDimensions) + putExtra(VIDEO_STABILIZE, stabilization) addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) } @@ -350,7 +393,9 @@ class VideoEditActivity : BaseThemedWithBarActivity() { binding.cropImageView.resetCropRect() cropRelativeDimensions = RelativeCropPosition() binding.cropper.drawable.setTintList(null) + binding.stabilizer.drawable.setTintList(null) binding.cropSavedCard.visibility = View.GONE + stabilization = 0f } override fun onDestroy() { @@ -414,6 +459,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() { const val VIDEO_START = "VideoEditVideoStartTag" const val VIDEO_END = "VideoEditVideoEndTag" const val VIDEO_CROP = "VideoEditVideoCropTag" + const val VIDEO_STABILIZE = "VideoEditVideoStabilizeTag" const val MODIFIED = "VideoEditModifiedTag" } } \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/settings/ThemeColorPreference.kt b/app/src/main/java/org/pixeldroid/app/settings/ThemeColorPreference.kt index 18a1ce44..74e5dd24 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/ThemeColorPreference.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/ThemeColorPreference.kt @@ -177,7 +177,7 @@ class ColorPickerView(context: Context?, attrs: AttributeSet? = null) : FrameLay binding.theme4.setOnClickListener { color = 3 } } - private fun changeConstraint(button2: View) { + private fun moveChoiceIndicator(button2: View) { binding.chosenTheme.isVisible = true val params = binding.chosenTheme.layoutParams as ConstraintLayout.LayoutParams params.endToEnd = button2.id @@ -185,8 +185,7 @@ class ColorPickerView(context: Context?, attrs: AttributeSet? = null) : FrameLay binding.chosenTheme.layoutParams = params binding.chosenTheme.requestLayout() } - /** Returns the color selected by the user */ - /** Sets the original color swatch and the current color to the specified value. */ + /** Color selected by the user */ var color: Int = 0 set(value) { field = value @@ -196,7 +195,7 @@ class ColorPickerView(context: Context?, attrs: AttributeSet? = null) : FrameLay 2 -> binding.theme3 3 -> binding.theme4 else -> null - }?.let { changeConstraint(it) } + }?.let { moveChoiceIndicator(it) } // Check switch if set to dynamic binding.dynamicColorSwitch.isChecked = value == -1 diff --git a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt index 23249264..708c65a1 100644 --- a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt +++ b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt @@ -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.convert(number: Float, target: ClosedRange): 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) diff --git a/app/src/main/res/drawable/video_stable.xml b/app/src/main/res/drawable/video_stable.xml new file mode 100644 index 00000000..7456637a --- /dev/null +++ b/app/src/main/res/drawable/video_stable.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_video_edit.xml b/app/src/main/res/layout/activity_video_edit.xml index cd018b28..7988bfa2 100644 --- a/app/src/main/res/layout/activity_video_edit.xml +++ b/app/src/main/res/layout/activity_video_edit.xml @@ -42,7 +42,6 @@ android:contentDescription="@string/save_crop" app:icon="@drawable/ic_crop_black_24dp"/> - + app:layout_constraintStart_toStartOf="parent"/> - - + app:layout_constraintStart_toEndOf="@+id/muter"/> + + + + + + + + + + + + + + + Error encoding Encode success! Encode %1$d%% + Stabilize video + Change intensity of stabilization + Stabilization saved + Analysis for stabilization %1$d%% Select what to keep of the video Mute video Change video speed diff --git a/build.gradle b/build.gradle index c9fe23a3..78010cad 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.0.0-alpha03' + classpath 'com.android.tools.build:gradle:8.0.0-alpha05' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 392c7177..e470ce24 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -504,6 +504,14 @@ + + + + + + + + @@ -552,6 +560,14 @@ + + + + + + + + @@ -592,6 +608,14 @@ + + + + + + + + @@ -640,6 +664,14 @@ + + + + + + + + @@ -683,6 +715,14 @@ + + + + + + + + @@ -726,6 +766,14 @@ + + + + + + + + @@ -769,6 +817,14 @@ + + + + + + + + @@ -2300,6 +2356,14 @@ + + + + + + + + @@ -2348,6 +2412,14 @@ + + + + + + + + @@ -2396,6 +2468,14 @@ + + + + + + + + @@ -2524,6 +2604,14 @@ + + + + + + + + @@ -2572,6 +2660,14 @@ + + + + + + + + @@ -2668,6 +2764,14 @@ + + + + + + + + @@ -2716,6 +2820,14 @@ + + + + + + + + @@ -2764,6 +2876,14 @@ + + + + + + + + @@ -2812,6 +2932,14 @@ + + + + + + + + @@ -2860,6 +2988,14 @@ + + + + + + + + @@ -2908,6 +3044,14 @@ + + + + + + + + @@ -2956,6 +3100,14 @@ + + + + + + + + @@ -3004,6 +3156,14 @@ + + + + + + + + @@ -3044,6 +3204,14 @@ + + + + + + + + @@ -3100,6 +3268,14 @@ + + + + + + + + @@ -3148,6 +3324,14 @@ + + + + + + + + @@ -3196,6 +3380,14 @@ + + + + + + + + @@ -3244,6 +3436,14 @@ + + + + + + + + @@ -3292,6 +3492,14 @@ + + + + + + + + @@ -3340,6 +3548,14 @@ + + + + + + + + @@ -3388,6 +3604,14 @@ + + + + + + + + @@ -3460,6 +3684,14 @@ + + + + + + + + @@ -3508,6 +3740,14 @@ + + + + + + + + @@ -3524,6 +3764,14 @@ + + + + + + + + @@ -3572,6 +3820,14 @@ + + + + + + + + @@ -3644,6 +3900,14 @@ + + + + + + + + @@ -3740,6 +4004,14 @@ + + + + + + + + @@ -3852,6 +4124,14 @@ + + + + + + + + @@ -3900,6 +4180,14 @@ + + + + + + + + @@ -3948,6 +4236,14 @@ + + + + + + + + @@ -3996,6 +4292,14 @@ + + + + + + + + @@ -4044,6 +4348,14 @@ + + + + + + + + @@ -4092,6 +4404,14 @@ + + + + + + + + @@ -4140,6 +4460,14 @@ + + + + + + + + @@ -4188,6 +4516,14 @@ + + + + + + + + @@ -4868,6 +5204,14 @@ + + + + + + + + @@ -4897,6 +5241,11 @@ + + + + + @@ -4905,6 +5254,11 @@ + + + + + @@ -4925,6 +5279,11 @@ + + + + + @@ -5063,6 +5422,11 @@ + + + + + @@ -5071,6 +5435,14 @@ + + + + + + + + @@ -5079,11 +5451,24 @@ + + + + + + + + + + + + + @@ -5631,6 +6016,14 @@ + + + + + + + + @@ -5639,6 +6032,14 @@ + + + + + + + + @@ -5647,6 +6048,14 @@ + + + + + + + + @@ -5655,6 +6064,14 @@ + + + + + + + + @@ -5663,6 +6080,14 @@ + + + + + + + + @@ -5671,6 +6096,14 @@ + + + + + + + + @@ -5679,6 +6112,14 @@ + + + + + + + + @@ -5687,6 +6128,14 @@ + + + + + + + + @@ -5695,6 +6144,14 @@ + + + + + + + + @@ -5703,6 +6160,14 @@ + + + + + + + + @@ -5711,6 +6176,14 @@ + + + + + + + + @@ -5719,6 +6192,14 @@ + + + + + + + + @@ -5727,6 +6208,14 @@ + + + + + + + + @@ -5735,6 +6224,14 @@ + + + + + + + + @@ -5743,11 +6240,24 @@ + + + + + + + + + + + + + @@ -5756,6 +6266,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -5764,6 +6295,14 @@ + + + + + + + + @@ -6127,6 +6666,11 @@ + + + + + @@ -6150,6 +6694,14 @@ + + + + + + + +