From 2f8c0e6ea1636489b4f424b7d2fd330a4a15458f Mon Sep 17 00:00:00 2001 From: tejpratapsingh Date: Fri, 12 Dec 2025 22:42:48 +0530 Subject: [PATCH] chore: dix all ktlint issues --- .idea/vcs.xml | 2 +- build.gradle | 14 ++ gradle/libs.versions.toml | 5 +- .../filamentrenderer/Filament3dView.kt | 43 +++-- .../FilamentOffscreenCapturer.kt | 108 ++++++++--- .../filamentrenderer/ExampleUnitTest.kt | 2 +- .../openglrenderer/MotionOpenGlView.kt | 30 ++-- .../motionlib/openglrenderer/ObjModel.kt | 41 +++-- .../Object3DToBitmapRenderer.kt | 157 +++++++++------- .../openglrenderer/OffscreenRenderer.kt | 71 +++++--- .../openglrenderer/ExampleUnitTest.kt | 2 +- .../animator/ExampleInstrumentedTest.kt | 2 +- .../activities/MotionPreviewActivity.kt | 14 +- .../animator/app/MyApplication.kt | 2 +- .../notification/NotificationChannelType.kt | 8 +- .../notification/NotificationFactory.kt | 31 ++-- .../presentation/SampleMotionVideo.kt | 49 ++--- .../animator/ui/view/ContourDevice.kt | 72 ++++---- .../animator/ui/view/MotionVideoContainer.kt | 167 ++++++++++-------- .../animator/ui/view/RenaultCar.kt | 52 +++--- .../tejpratapsingh/animator/utils/Timer.kt | 7 +- .../animator/worker/SampleMotionWorker.kt | 125 +++++++------ .../animator/ExampleUnitTest.kt | 2 +- .../motionlib/core/MotionAudio.kt | 8 +- .../motionlib/core/MotionConfig.kt | 4 +- .../motionlib/core/MotionEffect.kt | 2 +- .../motionlib/core/MotionPlugin.kt | 2 +- .../motionlib/core/MotionView.kt | 2 +- .../motionlib/core/OnMotionFrameListener.kt | 2 +- .../motionlib/core/VideoAspectRatio.kt | 27 ++- .../motionlib/core/VideoProducerAdapter.kt | 4 +- .../core/extensions/AssetExtension.kt | 3 +- .../core/extensions/BitmapExtension.kt | 6 +- .../core/extensions/ContextExtensions.kt | 25 +-- .../core/extensions/KtorExtension.kt | 26 +-- .../core/extensions/StringExtension.kt | 2 +- .../core/extensions/ViewExtension.kt | 12 +- .../motionlib/core/ExampleUnitTest.kt | 2 +- .../ffmpeg/FfmpegVideoProducerAdapter.kt | 65 ++++--- .../ffmpeg/utils/FFMpegExtensions.kt | 6 +- .../ffmpeg/video/FFMpegVideoFrameView.kt | 34 ++-- .../motionlib/ffmpeg/ExampleUnitTest.kt | 2 +- .../ivi_demo/ExampleInstrumentedTest.kt | 2 +- .../tejpratapsingh/ivi_demo/MainActivity.kt | 28 +-- .../ivi_demo/extension/ViewExtensions.kt | 12 +- .../ivi_demo/motion/RenaultCar.kt | 111 +++++++----- .../tejpratapsingh/ivi_demo/motion/Road.kt | 38 ++-- .../ivi_demo/sequence/RenaultSequence.kt | 41 +++-- .../ivi_demo/view/TrapezoidImageView.kt | 81 +++++---- .../ivi_demo/ExampleUnitTest.kt | 2 +- .../motionlib/jcodec/FileExtension.kt | 6 +- .../jcodec/JCodecVideoProducerAdapter.kt | 9 +- .../motionlib/jcodec/ExampleUnitTest.kt | 2 +- .../lyricsmaker/ExampleInstrumentedTest.kt | 2 +- .../data/api/client/AlbumArtFetcher.kt | 77 ++++---- .../data/api/client/LrcLibClient.kt | 42 +++-- .../lyricsmaker/data/api/model/LyricsQuery.kt | 4 +- .../data/api/model/LyricsResponse.kt | 5 +- .../lyricsmaker/data/api/model/SearchQuery.kt | 4 +- .../lyricsmaker/data/lrc/LrcHelper.kt | 47 ++--- .../lyricsmaker/data/lrc/LrcLine.kt | 4 +- .../lyricsmaker/data/lrc/LrcParser.kt | 8 +- .../lyricsmaker/data/lrc/SyncedLyricFrame.kt | 2 +- .../data/store/RecentSearchHelper.kt | 13 +- .../lyricsmaker/domain/ListExtensions.kt | 4 +- .../presentation/activity/LyricsActivity.kt | 59 ++++--- .../presentation/activity/SearchActivity.kt | 12 +- .../presentation/compose/AppNavHost.kt | 16 +- .../compose/SearchLyricsCompose.kt | 139 ++++++++------- .../compose/SyncedLyricsSelector.kt | 74 ++++---- .../motion/LyricsVideoProducer.kt | 41 +++-- .../motion/MultiLyricsVideoProducer.kt | 41 +++-- .../notification/NotificationChannelType.kt | 8 +- .../notification/NotificationFactory.kt | 31 ++-- .../presentation/ui/theme/Color.kt | 2 +- .../presentation/ui/theme/Theme.kt | 53 +++--- .../lyricsmaker/presentation/ui/theme/Type.kt | 22 +-- .../presentation/view/FakeAudioChartView.kt | 145 ++++++++------- .../presentation/view/FakeSineWaveView.kt | 105 +++++------ .../presentation/view/LyricsContainer.kt | 43 ++--- .../presentation/view/LyricsTextView.kt | 19 +- .../presentation/view/SongNameTextView.kt | 5 +- .../presentation/viewmodel/LyricsViewModel.kt | 43 ++--- .../presentation/worker/LyricsMotionWorker.kt | 127 ++++++++----- .../lyricsmaker/ExampleUnitTest.kt | 2 +- .../ExampleInstrumentedTest.kt | 8 +- .../metadataextractor/MetadataExtractor.kt | 4 +- .../ShareReceiverActivity.kt | 22 +-- .../metadataextractor/ExampleUnitTest.kt | 5 +- .../motionlib/activities/PreviewActivity.kt | 6 +- .../adapter/AndroidVideoProducerAdapter.kt | 15 +- .../motionlib/core/animation/Easings.kt | 2 +- .../motionlib/core/animation/Interpolators.kt | 83 ++++----- .../core/animation/MotionInterpolator.kt | 18 +- .../motionlib/core/animation/Spring.kt | 97 +++++----- .../core/infra/AndroidVideoGenerator.kt | 96 +++++----- .../motionlib/core/infra/AudioProcessor.kt | 12 +- .../core/motion/BaseContourMotionView.kt | 7 +- .../core/motion/BaseFrameMotionView.kt | 134 +++++++------- .../motionlib/core/motion/IComposerView.kt | 2 +- .../core/motion/IMotionVideoProducer.kt | 5 +- .../core/motion/MotionComposerView.kt | 23 +-- .../core/motion/MotionVideoProducer.kt | 63 ++++--- .../core/motion/OrientedMotionView.kt | 4 +- .../motionlib/ui/MotionVideoPlayer.kt | 167 ++++++++++-------- .../motionlib/ui/custom/CutoutTextView.kt | 69 ++++---- .../ui/custom/audio/BaseAudioWaveformView.kt | 31 ++-- .../custom/audio/CircularAudioWaveformView.kt | 5 +- .../custom/audio/RadialAudioWaveformView.kt | 5 +- .../ui/custom/background/GradientView.kt | 108 ++++++----- .../ui/custom/container/RotatingMotionView.kt | 26 +-- .../ui/custom/text/TransparentTextView.kt | 19 +- .../ui/custom/text/TypeWriterTextView.kt | 20 ++- .../ui/custom/text/WordBlinkTextView.kt | 20 ++- .../ui/custom/text/WordWriterTextView.kt | 20 ++- .../text/abstract/AbstractMotionTextView.kt | 28 +-- .../ui/custom/video/VideoFrameView.kt | 29 +-- .../ui/effects/SlideRightToLeftEffect.kt | 17 +- .../motionlib/utils/TextViewUtil.kt | 5 +- .../motionlib/utils/VideoUtil.kt | 32 ++-- .../motionlib/worker/MotionWorker.kt | 71 ++++---- .../motionlib/ExampleUnitTest.kt | 2 +- .../pytorch/PyTorchImageProcessor.kt | 10 +- .../motionlib/pytorch/common/ModelTypes.kt | 8 +- .../motionlib/pytorch/removebg/RemoveBg.kt | 35 ++-- .../motionlib/pytorch/removebg/Remover.kt | 9 +- .../pytorch/superres/ImageUpscaler.kt | 21 ++- .../motionlib/pytorch/superres/SuperRes.kt | 28 +-- .../motionlib/pytorch/utils/FileUtils.kt | 7 +- .../motionlib/pytorch/utils/NetUtils.kt | 8 +- .../motionlib/pytorch/ExampleUnitTest.kt | 2 +- .../motion/sdui/ExampleInstrumentedTest.kt | 8 +- .../motion/sdui/data/SduiRenderer.kt | 19 +- .../motion/sdui/domain/ActionHandler.kt | 7 +- .../motion/sdui/domain/ImageLoader.kt | 7 +- .../motion/sdui/domain/ViewFactory.kt | 8 +- .../sdui/presentation/ContainerFactory.kt | 15 +- .../sdui/presentation/DefaultActionHandler.kt | 7 +- .../motion/sdui/presentation/ImageFactory.kt | 6 +- .../motion/sdui/presentation/TextFactory.kt | 9 +- .../motion/sdui/ExampleUnitTest.kt | 5 +- .../motionlib/templates/ExampleUnitTest.kt | 2 +- .../motionlib/tensorflow/ImageUtils.kt | 5 +- .../tensorflow/TensorFlowImageProcessor.kt | 8 +- .../tensorflow/removebg/CarBgRemover.kt | 33 ++-- .../removebg/TfLiteSegmentationHelper.kt | 38 ++-- .../removebg/TiledBackgroundRemover.kt | 7 +- .../superres/SuperResolutionProcessor.kt | 15 +- .../motionlib/tensorflow/ExampleUnitTest.kt | 2 +- 149 files changed, 2485 insertions(+), 1895 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7f..35eb1ddf 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 2fe43efa..d38202e3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import org.jlleitschuh.gradle.ktlint.reporter.ReporterType + // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { @@ -5,7 +7,19 @@ buildscript { mavenCentral() } dependencies { + classpath libs.ktlint.gradle } } plugins { + alias(libs.plugins.ktlint) +} +ktlint { + android.set(true) // set true for Android projects + outputToConsole.set(true) + ignoreFailures.set(false) + enableExperimentalRules.set(false) + reporters { + reporter(ReporterType.PLAIN) + reporter(ReporterType.CHECKSTYLE) + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 277ba6d9..736ec4ed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ work_version = "2.10.3" junit_version = "4.13.2" androidx_test_ext_junit_version = "1.3.0" espresso_core_version = "3.7.0" -agpVersion = "8.13.1" +agpVersion = "8.13.2" kotlinVersion = "2.2.10" pytorchAndroidTorchCisionLite = "2.1.0" kotlinGradlePluginVersion = "2.2.0" @@ -27,6 +27,7 @@ gson = "2.13.1" activityComposeVersion = "1.10.1" composeBomVersion = "2024.09.00" navigationComposeVersion = "2.9.3" +ktlint = "14.0.1" [libraries] androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayoutVersion" } @@ -48,6 +49,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoupVersion" } junit = { group = "junit", name = "junit", version.ref = "junit_version" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx_test_ext_junit_version" } androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso_core_version" } +ktlint-gradle = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientCoreVersion" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorClientCoreVersion" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCoreVersion" } @@ -81,6 +83,7 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinVer kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinVersion" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlinVersion" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlinVersion" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } [bundles] androidx = [ diff --git a/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/Filament3dView.kt b/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/Filament3dView.kt index 04058846..374c35a7 100644 --- a/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/Filament3dView.kt +++ b/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/Filament3dView.kt @@ -31,9 +31,9 @@ class Filament3dView( private val modelAssetPath: String, override val startFrame: Int, override val endFrame: Int, - override val loop: Pair = Pair(0, 0) -) : FrameLayout(context), MotionView { - + override val loop: Pair = Pair(0, 0), +) : FrameLayout(context), + MotionView { override val effects: List = emptyList() companion object { @@ -50,9 +50,10 @@ class Filament3dView( private lateinit var surfaceTexture: SurfaceTexture private lateinit var surface: Surface - private val imageView = ImageView(context).apply { - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - } + private val imageView = + ImageView(context).apply { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + } init { initializeFilament() @@ -67,7 +68,7 @@ class Filament3dView( surfaceTexture = SurfaceTexture(0) surfaceTexture.setDefaultBufferSize( MotionConfig.aspectRatio.width, - MotionConfig.aspectRatio.height + MotionConfig.aspectRatio.height, ) surface = Surface(surfaceTexture) engine = Engine.create() @@ -109,12 +110,18 @@ class Filament3dView( (MotionConfig.aspectRatio.width / MotionConfig.aspectRatio.height).toDouble(), 0.1, 1000.0, - Camera.Fov.VERTICAL + Camera.Fov.VERTICAL, ) camera.lookAt( - 0.0, 0.0, 5.0, // eyeX, eyeY, eyeZ - 0.0, 0.0, 0.0, // centerX, centerY, centerZ - 0.0, 1.0, 0.0 // upX, upY, upZ + 0.0, + 0.0, + 5.0, // eyeX, eyeY, eyeZ + 0.0, + 0.0, + 0.0, // centerX, centerY, centerZ + 0.0, + 1.0, + 0.0, // upX, upY, upZ ) view.camera = camera } @@ -136,9 +143,15 @@ class Filament3dView( val height = view.viewport.height val buffer = ByteBuffer.allocateDirect(width * height * 4) renderer.readPixels( - 0, 0, width, height, Texture.PixelBufferDescriptor( - buffer, Texture.Format.RGBA, Texture.Type.UBYTE - ) + 0, + 0, + width, + height, + Texture.PixelBufferDescriptor( + buffer, + Texture.Format.RGBA, + Texture.Type.UBYTE, + ), ) buffer.rewind() renderer.endFrame() @@ -190,4 +203,4 @@ class Filament3dView( fun destroy() { cleanupFilament() } -} \ No newline at end of file +} diff --git a/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/FilamentOffscreenCapturer.kt b/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/FilamentOffscreenCapturer.kt index 2fab58df..70f9c1c7 100644 --- a/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/FilamentOffscreenCapturer.kt +++ b/modules/3d-filament-renderer/src/main/java/com/tejpratapsingh/motionlib/filamentrenderer/FilamentOffscreenCapturer.kt @@ -24,7 +24,9 @@ import java.nio.ByteOrder import kotlin.math.cos import kotlin.math.sin -class FilamentOffscreenCapturer(private val context: Context) { +class FilamentOffscreenCapturer( + private val context: Context, +) { private lateinit var engine: Engine private var renderer: Renderer? = null private var scene: Scene? = null @@ -39,10 +41,15 @@ class FilamentOffscreenCapturer(private val context: Context) { private var asset: FilamentAsset? = null enum class RotationAxis { - X, Y, Z + X, + Y, + Z, } - fun init(width: Int, height: Int) { + fun init( + width: Int, + height: Int, + ) { engine = Engine.create() renderer = engine.createRenderer() @@ -65,12 +72,18 @@ class FilamentOffscreenCapturer(private val context: Context) { width.toDouble() / height.toDouble(), 0.1, 100.0, - Camera.Fov.VERTICAL + Camera.Fov.VERTICAL, ) camera?.lookAt( - 0.0, 0.0, 3.0, // eye - 0.0, 0.0, 0.0, // center - 0.0, 1.0, 0.0 // up + 0.0, + 0.0, + 3.0, // eye + 0.0, + 0.0, + 0.0, // center + 0.0, + 1.0, + 0.0, // up ) val materialProvider = UbershaderProvider(engine) @@ -108,7 +121,10 @@ class FilamentOffscreenCapturer(private val context: Context) { scene?.addEntities(asset!!.entities) } - fun setRotation(axis: RotationAxis, degrees: Float) { + fun setRotation( + axis: RotationAxis, + degrees: Float, + ) { asset?.let { val radians = Math.toRadians(degrees.toDouble()) val cos = cos(radians).toFloat() @@ -118,26 +134,62 @@ class FilamentOffscreenCapturer(private val context: Context) { when (axis) { RotationAxis.X -> { // Rotate around X-axis - transform[0] = 1f; transform[4] = 0f; transform[8] = 0f; transform[12] = 0f - transform[1] = 0f; transform[5] = cos; transform[9] = -sin; transform[13] = 0f - transform[2] = 0f; transform[6] = sin; transform[10] = cos; transform[14] = 0f - transform[3] = 0f; transform[7] = 0f; transform[11] = 0f; transform[15] = 1f + transform[0] = 1f + transform[4] = 0f + transform[8] = 0f + transform[12] = 0f + transform[1] = 0f + transform[5] = cos + transform[9] = -sin + transform[13] = 0f + transform[2] = 0f + transform[6] = sin + transform[10] = cos + transform[14] = 0f + transform[3] = 0f + transform[7] = 0f + transform[11] = 0f + transform[15] = 1f } RotationAxis.Y -> { // Rotate around Y-axis - transform[0] = cos; transform[4] = 0f; transform[8] = sin; transform[12] = 0f - transform[1] = 0f; transform[5] = 1f; transform[9] = 0f; transform[13] = 0f - transform[2] = -sin; transform[6] = 0f; transform[10] = cos; transform[14] = 0f - transform[3] = 0f; transform[7] = 0f; transform[11] = 0f; transform[15] = 1f + transform[0] = cos + transform[4] = 0f + transform[8] = sin + transform[12] = 0f + transform[1] = 0f + transform[5] = 1f + transform[9] = 0f + transform[13] = 0f + transform[2] = -sin + transform[6] = 0f + transform[10] = cos + transform[14] = 0f + transform[3] = 0f + transform[7] = 0f + transform[11] = 0f + transform[15] = 1f } RotationAxis.Z -> { // Rotate around Z-axis - transform[0] = cos; transform[4] = -sin; transform[8] = 0f; transform[12] = 0f - transform[1] = sin; transform[5] = cos; transform[9] = 0f; transform[13] = 0f - transform[2] = 0f; transform[6] = 0f; transform[10] = 1f; transform[14] = 0f - transform[3] = 0f; transform[7] = 0f; transform[11] = 0f; transform[15] = 1f + transform[0] = cos + transform[4] = -sin + transform[8] = 0f + transform[12] = 0f + transform[1] = sin + transform[5] = cos + transform[9] = 0f + transform[13] = 0f + transform[2] = 0f + transform[6] = 0f + transform[10] = 1f + transform[14] = 0f + transform[3] = 0f + transform[7] = 0f + transform[11] = 0f + transform[15] = 1f } } @@ -161,7 +213,10 @@ class FilamentOffscreenCapturer(private val context: Context) { setRotation(RotationAxis.Z, degrees) } - fun capture(width: Int, height: Int): Bitmap? { + fun capture( + width: Int, + height: Int, + ): Bitmap? { try { if (renderer?.beginFrame(swapChain!!, 0L) == true) { renderer?.render(view!!) @@ -170,11 +225,12 @@ class FilamentOffscreenCapturer(private val context: Context) { val pixelCount = width * height val buf = ByteBuffer.allocateDirect(pixelCount * 4).order(ByteOrder.nativeOrder()) - val descriptor = Texture.PixelBufferDescriptor( - buf, - Texture.Format.RGBA, - Texture.Type.UBYTE - ) + val descriptor = + Texture.PixelBufferDescriptor( + buf, + Texture.Format.RGBA, + Texture.Type.UBYTE, + ) renderer?.readPixels(0, 0, width, height, descriptor) buf.rewind() diff --git a/modules/3d-filament-renderer/src/test/java/com/tejpratapsingh/motionlib/filamentrenderer/ExampleUnitTest.kt b/modules/3d-filament-renderer/src/test/java/com/tejpratapsingh/motionlib/filamentrenderer/ExampleUnitTest.kt index 18e34b09..38697d2e 100644 --- a/modules/3d-filament-renderer/src/test/java/com/tejpratapsingh/motionlib/filamentrenderer/ExampleUnitTest.kt +++ b/modules/3d-filament-renderer/src/test/java/com/tejpratapsingh/motionlib/filamentrenderer/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/MotionOpenGlView.kt b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/MotionOpenGlView.kt index d5adc770..c0acdead 100644 --- a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/MotionOpenGlView.kt +++ b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/MotionOpenGlView.kt @@ -13,24 +13,26 @@ class MotionOpenGlView( modelAssetPath: String, override val startFrame: Int, override val endFrame: Int, - override val loop: Pair = Pair(0, 0) -) : FrameLayout(context), MotionView { - + override val loop: Pair = Pair(0, 0), +) : FrameLayout(context), + MotionView { override val effects: List = emptyList() - private val imageView = ImageView(context).apply { - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - } + private val imageView = + ImageView(context).apply { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + } // private val offscreenRenderer: OffscreenRenderer - val offscreenRenderer = Object3DToBitmapRenderer( - context = context, - assetFileName = modelAssetPath, - width = MotionConfig.aspectRatio.width, - height = MotionConfig.aspectRatio.height, - objectColor = floatArrayOf(0.7f, 0.3f, 0.3f, 1.0f) - ) + val offscreenRenderer = + Object3DToBitmapRenderer( + context = context, + assetFileName = modelAssetPath, + width = MotionConfig.aspectRatio.width, + height = MotionConfig.aspectRatio.height, + objectColor = floatArrayOf(0.7f, 0.3f, 0.3f, 1.0f), + ) init { // Initialize OpenGL renderer with the model asset path @@ -53,4 +55,4 @@ class MotionOpenGlView( } override fun getViewBitmap(): Bitmap = offscreenRenderer.generateBitmap()!! -} \ No newline at end of file +} diff --git a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/ObjModel.kt b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/ObjModel.kt index 01c62ff0..6dbbe229 100644 --- a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/ObjModel.kt +++ b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/ObjModel.kt @@ -8,7 +8,10 @@ import java.nio.ByteOrder import java.nio.FloatBuffer import java.nio.ShortBuffer -class ObjModel(context: Context, filename: String) { +class ObjModel( + context: Context, + filename: String, +) { val vertexBuffer: FloatBuffer val indexBuffer: ShortBuffer val indexCount: Int @@ -35,7 +38,7 @@ class ObjModel(context: Context, filename: String) { for (i in 1..3) { val indexStr = parts[i].split("/")[0] val idx = indexStr.toInt() - indices.add((idx - 1).toShort()) // OBJ is 1-based + indices.add((idx - 1).toShort()) // OBJ is 1-based } } } @@ -45,22 +48,26 @@ class ObjModel(context: Context, filename: String) { vertices.addAll(it.toList()) } - vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4) - .order(ByteOrder.nativeOrder()) - .asFloatBuffer() - .apply { - put(vertices.toFloatArray()) - position(0) - } + vertexBuffer = + ByteBuffer + .allocateDirect(vertices.size * 4) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer() + .apply { + put(vertices.toFloatArray()) + position(0) + } - indexBuffer = ByteBuffer.allocateDirect(indices.size * 2) - .order(ByteOrder.nativeOrder()) - .asShortBuffer() - .apply { - put(indices.toShortArray()) - position(0) - } + indexBuffer = + ByteBuffer + .allocateDirect(indices.size * 2) + .order(ByteOrder.nativeOrder()) + .asShortBuffer() + .apply { + put(indices.toShortArray()) + position(0) + } indexCount = indices.size } -} \ No newline at end of file +} diff --git a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/Object3DToBitmapRenderer.kt b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/Object3DToBitmapRenderer.kt index e5905bd7..54df692d 100644 --- a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/Object3DToBitmapRenderer.kt +++ b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/Object3DToBitmapRenderer.kt @@ -23,9 +23,8 @@ class Object3DToBitmapRenderer( private val assetFileName: String, private val width: Int, private val height: Int, - private val objectColor: FloatArray = floatArrayOf(0.8f, 0.8f, 0.8f, 1.0f) + private val objectColor: FloatArray = floatArrayOf(0.8f, 0.8f, 0.8f, 1.0f), ) { - private var egl: EGL10? = null private var eglDisplay: EGLDisplay? = null private var eglContext: EGLContext? = null @@ -50,10 +49,11 @@ class Object3DToBitmapRenderer( val vertices: FloatBuffer, val normals: FloatBuffer, val indices: ShortBuffer, - val indexCount: Int + val indexCount: Int, ) - private val vertexShaderCode = """ + private val vertexShaderCode = + """ attribute vec4 vPosition; attribute vec3 vNormal; uniform mat4 uMVPMatrix; @@ -72,9 +72,10 @@ class Object3DToBitmapRenderer( diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance))); vLightIntensity = diffuse; } - """.trimIndent() + """.trimIndent() - private val fragmentShaderCode = """ + private val fragmentShaderCode = + """ precision mediump float; uniform vec4 uColor; varying float vLightIntensity; @@ -82,14 +83,14 @@ class Object3DToBitmapRenderer( void main() { gl_FragColor = vec4(uColor.rgb * vLightIntensity, uColor.a); } - """.trimIndent() + """.trimIndent() /** * Initialize the renderer - call this once before generating bitmaps * @return true if initialization successful, false otherwise */ - fun initialize(): Boolean { - return try { + fun initialize(): Boolean = + try { setupOffscreenRendering(width, height) mesh = loadObjFromAssets(assetFileName) setupShaders() @@ -101,7 +102,6 @@ class Object3DToBitmapRenderer( cleanup() false } - } /** * Set the rotation angles for the 3D model @@ -112,7 +112,7 @@ class Object3DToBitmapRenderer( fun setRotation( rotationX: Float = 0f, rotationY: Float = 0f, - rotationZ: Float = 0f + rotationZ: Float = 0f, ) { if (!isInitialized) { throw IllegalStateException("Renderer not initialized. Call initialize() first.") @@ -152,7 +152,7 @@ class Object3DToBitmapRenderer( fun generateBitmapWithRotation( rotationX: Float = 0f, rotationY: Float = 0f, - rotationZ: Float = 0f + rotationZ: Float = 0f, ): Bitmap? { setRotation(rotationX, rotationY, rotationZ) return generateBitmap() @@ -167,44 +167,60 @@ class Object3DToBitmapRenderer( mesh = null } - private fun setupOffscreenRendering(width: Int, height: Int) { + private fun setupOffscreenRendering( + width: Int, + height: Int, + ) { egl = EGL10.EGL_NO_CONTEXT as EGL10 eglDisplay = egl!!.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) val version = IntArray(2) egl!!.eglInitialize(eglDisplay, version) - val configAttribs = intArrayOf( - EGL10.EGL_RENDERABLE_TYPE, 4, // EGL_OPENGL_ES2_BIT - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, - EGL10.EGL_DEPTH_SIZE, 16, - EGL10.EGL_NONE - ) + val configAttribs = + intArrayOf( + EGL10.EGL_RENDERABLE_TYPE, + 4, // EGL_OPENGL_ES2_BIT + EGL10.EGL_RED_SIZE, + 8, + EGL10.EGL_GREEN_SIZE, + 8, + EGL10.EGL_BLUE_SIZE, + 8, + EGL10.EGL_ALPHA_SIZE, + 8, + EGL10.EGL_DEPTH_SIZE, + 16, + EGL10.EGL_NONE, + ) val configs = arrayOfNulls(1) val numConfigs = IntArray(1) egl!!.eglChooseConfig(eglDisplay, configAttribs, configs, 1, numConfigs) - val contextAttribs = intArrayOf( - 0x3098, 2, // EGL_CONTEXT_CLIENT_VERSION - EGL10.EGL_NONE - ) + val contextAttribs = + intArrayOf( + 0x3098, + 2, // EGL_CONTEXT_CLIENT_VERSION + EGL10.EGL_NONE, + ) - eglContext = egl!!.eglCreateContext( - eglDisplay, - configs[0], - EGL10.EGL_NO_CONTEXT, - contextAttribs - ) + eglContext = + egl!!.eglCreateContext( + eglDisplay, + configs[0], + EGL10.EGL_NO_CONTEXT, + contextAttribs, + ) - val surfaceAttribs = intArrayOf( - EGL10.EGL_WIDTH, width, - EGL10.EGL_HEIGHT, height, - EGL10.EGL_NONE - ) + val surfaceAttribs = + intArrayOf( + EGL10.EGL_WIDTH, + width, + EGL10.EGL_HEIGHT, + height, + EGL10.EGL_NONE, + ) eglSurface = egl!!.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs) egl!!.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) @@ -230,8 +246,8 @@ class Object3DToBitmapRenderer( floatArrayOf( parts[1].toFloat(), parts[2].toFloat(), - parts[3].toFloat() - ) + parts[3].toFloat(), + ), ) } @@ -241,8 +257,8 @@ class Object3DToBitmapRenderer( floatArrayOf( parts[1].toFloat(), parts[2].toFloat(), - parts[3].toFloat() - ) + parts[3].toFloat(), + ), ) } @@ -254,7 +270,9 @@ class Object3DToBitmapRenderer( val normalIndex = if (vertexData.size > 2 && vertexData[2].isNotEmpty()) { vertexData[2].toInt() - 1 - } else vertexIndex + } else { + vertexIndex + } // Add vertex vertices.addAll(tempVertices[vertexIndex].toList()) @@ -283,11 +301,14 @@ class Object3DToBitmapRenderer( vertices = createFloatBuffer(vertices.toFloatArray()), normals = createFloatBuffer(normals.toFloatArray()), indices = createShortBuffer(indices.toShortArray()), - indexCount = indices.size + indexCount = indices.size, ) } - private fun calculateNormals(vertices: FloatArray, indices: ShortArray): FloatArray { + private fun calculateNormals( + vertices: FloatArray, + indices: ShortArray, + ): FloatArray { val normals = FloatArray(vertices.size) // Calculate face normals and accumulate vertex normals @@ -303,11 +324,12 @@ class Object3DToBitmapRenderer( val edge1 = floatArrayOf(v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]) val edge2 = floatArrayOf(v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2]) - val normal = floatArrayOf( - edge1[1] * edge2[2] - edge1[2] * edge2[1], - edge1[2] * edge2[0] - edge1[0] * edge2[2], - edge1[0] * edge2[1] - edge1[1] * edge2[0] - ) + val normal = + floatArrayOf( + edge1[1] * edge2[2] - edge1[2] * edge2[1], + edge1[2] * edge2[0] - edge1[0] * edge2[2], + edge1[0] * edge2[1] - edge1[1] * edge2[0], + ) // Normalize val length = sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]) @@ -340,21 +362,21 @@ class Object3DToBitmapRenderer( return normals } - private fun createFloatBuffer(array: FloatArray): FloatBuffer { - return ByteBuffer.allocateDirect(array.size * 4) + private fun createFloatBuffer(array: FloatArray): FloatBuffer = + ByteBuffer + .allocateDirect(array.size * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer() .put(array) .apply { position(0) } - } - private fun createShortBuffer(array: ShortArray): ShortBuffer { - return ByteBuffer.allocateDirect(array.size * 2) + private fun createShortBuffer(array: ShortArray): ShortBuffer = + ByteBuffer + .allocateDirect(array.size * 2) .order(ByteOrder.nativeOrder()) .asShortBuffer() .put(array) .apply { position(0) } - } private fun setupShaders() { val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode) @@ -372,14 +394,20 @@ class Object3DToBitmapRenderer( colorHandle = GLES20.glGetUniformLocation(shaderProgram, "uColor") } - private fun loadShader(type: Int, shaderCode: String): Int { + private fun loadShader( + type: Int, + shaderCode: String, + ): Int { val shader = GLES20.glCreateShader(type) GLES20.glShaderSource(shader, shaderCode) GLES20.glCompileShader(shader) return shader } - private fun setupMatrices(width: Int, height: Int) { + private fun setupMatrices( + width: Int, + height: Int, + ) { // Set up projection matrix val ratio = width.toFloat() / height.toFloat() Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f) @@ -391,7 +419,12 @@ class Object3DToBitmapRenderer( Matrix.setIdentityM(modelMatrix, 0) } - private fun renderToBitmap(mesh: Mesh, width: Int, height: Int, color: FloatArray): Bitmap { + private fun renderToBitmap( + mesh: Mesh, + width: Int, + height: Int, + color: FloatArray, + ): Bitmap { GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f) // White background GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT) GLES20.glEnable(GLES20.GL_DEPTH_TEST) @@ -420,7 +453,7 @@ class Object3DToBitmapRenderer( GLES20.GL_TRIANGLES, mesh.indexCount, GLES20.GL_UNSIGNED_SHORT, - mesh.indices + mesh.indices, ) // Read pixels @@ -432,7 +465,7 @@ class Object3DToBitmapRenderer( height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, - pixelBuffer + pixelBuffer, ) // Convert to bitmap @@ -453,7 +486,7 @@ class Object3DToBitmapRenderer( eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT + EGL10.EGL_NO_CONTEXT, ) eglSurface?.let { egl.eglDestroySurface(eglDisplay, it) } eglContext?.let { egl.eglDestroyContext(eglDisplay, it) } @@ -471,4 +504,4 @@ class Object3DToBitmapRenderer( eglSurface = null shaderProgram = 0 } -} \ No newline at end of file +} diff --git a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/OffscreenRenderer.kt b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/OffscreenRenderer.kt index 39833432..399fb086 100644 --- a/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/OffscreenRenderer.kt +++ b/modules/3d-opengl-renderer/src/main/java/com/tejpratapsingh/motionlib/openglrenderer/OffscreenRenderer.kt @@ -10,47 +10,61 @@ import javax.microedition.khronos.egl.EGL10 import javax.microedition.khronos.egl.EGLConfig import javax.microedition.khronos.egl.EGLContext -class OffscreenRenderer(private val model: ObjModel) { +class OffscreenRenderer( + private val model: ObjModel, +) { private val modelMatrix = FloatArray(16) private val viewMatrix = FloatArray(16) private val projectionMatrix = FloatArray(16) private val mvpMatrix = FloatArray(16) private var rotationAngle = 0f - private val vertexShaderCode = """ + private val vertexShaderCode = + """ uniform mat4 uMVPMatrix; attribute vec4 vPosition; void main() { gl_Position = uMVPMatrix * vPosition; } - """.trimIndent() + """.trimIndent() - private val fragmentShaderCode = """ + private val fragmentShaderCode = + """ precision mediump float; uniform vec4 vColor; void main() { gl_FragColor = vColor; } - """.trimIndent() + """.trimIndent() fun setRotation(angleInDegrees: Float) { rotationAngle = angleInDegrees } - fun renderOffscreen(width: Int = 512, height: Int = 512): Bitmap { + fun renderOffscreen( + width: Int = 512, + height: Int = 512, + ): Bitmap { val egl = EGLContext.getEGL() as EGL10 val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) egl.eglInitialize(display, null) - val attribList = intArrayOf( - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, - EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, - EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, - EGL10.EGL_NONE - ) + val attribList = + intArrayOf( + EGL10.EGL_RED_SIZE, + 8, + EGL10.EGL_GREEN_SIZE, + 8, + EGL10.EGL_BLUE_SIZE, + 8, + EGL10.EGL_ALPHA_SIZE, + 8, + EGL10.EGL_SURFACE_TYPE, + EGL10.EGL_PBUFFER_BIT, + EGL10.EGL_RENDERABLE_TYPE, + EGL14.EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE, + ) val configs = arrayOfNulls(1) val numConfigs = IntArray(1) @@ -60,11 +74,14 @@ class OffscreenRenderer(private val model: ObjModel) { val attribList1 = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE) val context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attribList1) - val surfaceAttribs = intArrayOf( - EGL10.EGL_WIDTH, width, - EGL10.EGL_HEIGHT, height, - EGL10.EGL_NONE - ) + val surfaceAttribs = + intArrayOf( + EGL10.EGL_WIDTH, + width, + EGL10.EGL_HEIGHT, + height, + EGL10.EGL_NONE, + ) val surface = egl.eglCreatePbufferSurface(display, config, surfaceAttribs) egl.eglMakeCurrent(display, surface, surface, context) @@ -101,7 +118,7 @@ class OffscreenRenderer(private val model: ObjModel) { GLES20.GL_TRIANGLES, model.indexCount, GLES20.GL_UNSIGNED_SHORT, - model.indexBuffer + model.indexBuffer, ) GLES20.glDisableVertexAttribArray(posHandle) @@ -116,7 +133,7 @@ class OffscreenRenderer(private val model: ObjModel) { display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT + EGL10.EGL_NO_CONTEXT, ) egl.eglDestroySurface(display, surface) egl.eglDestroyContext(display, context) @@ -143,8 +160,11 @@ class OffscreenRenderer(private val model: ObjModel) { } } - private fun loadShader(type: Int, code: String): Int { - return GLES20.glCreateShader(type).also { + private fun loadShader( + type: Int, + code: String, + ): Int = + GLES20.glCreateShader(type).also { GLES20.glShaderSource(it, code) GLES20.glCompileShader(it) val compiled = IntArray(1) @@ -155,5 +175,4 @@ class OffscreenRenderer(private val model: ObjModel) { throw RuntimeException("Could not compile shader $type: $log") } } - } -} \ No newline at end of file +} diff --git a/modules/3d-opengl-renderer/src/test/java/com/tejpratapsingh/motionlib/openglrenderer/ExampleUnitTest.kt b/modules/3d-opengl-renderer/src/test/java/com/tejpratapsingh/motionlib/openglrenderer/ExampleUnitTest.kt index c5683e0b..050516f6 100644 --- a/modules/3d-opengl-renderer/src/test/java/com/tejpratapsingh/motionlib/openglrenderer/ExampleUnitTest.kt +++ b/modules/3d-opengl-renderer/src/test/java/com/tejpratapsingh/motionlib/openglrenderer/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/app/src/androidTest/java/com/tejpratapsingh/animator/ExampleInstrumentedTest.kt b/modules/app/src/androidTest/java/com/tejpratapsingh/animator/ExampleInstrumentedTest.kt index f67b35b9..b5da221d 100644 --- a/modules/app/src/androidTest/java/com/tejpratapsingh/animator/ExampleInstrumentedTest.kt +++ b/modules/app/src/androidTest/java/com/tejpratapsingh/animator/ExampleInstrumentedTest.kt @@ -19,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.tejpratapsingh.animator", appContext.packageName) } -} \ No newline at end of file +} diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/activities/MotionPreviewActivity.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/activities/MotionPreviewActivity.kt index b2f5660e..792b3831 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/activities/MotionPreviewActivity.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/activities/MotionPreviewActivity.kt @@ -11,7 +11,6 @@ import com.tejpratapsingh.motionlib.activities.PreviewActivity import com.tejpratapsingh.motionlib.core.motion.MotionVideoProducer class MotionPreviewActivity : PreviewActivity() { - val video by lazy { sampleMotionVideo(applicationContext) } @@ -21,11 +20,14 @@ class MotionPreviewActivity : PreviewActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ActivityCompat.checkSelfPermission( - this, Manifest.permission.POST_NOTIFICATIONS + this, + Manifest.permission.POST_NOTIFICATIONS, ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( - this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0 + this, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + 0, ) } } @@ -33,7 +35,5 @@ class MotionPreviewActivity : PreviewActivity() { SampleMotionWorker.startWork(applicationContext) } - override fun getMotionVideo(): MotionVideoProducer { - return video - } -} \ No newline at end of file + override fun getMotionVideo(): MotionVideoProducer = video +} diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/app/MyApplication.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/app/MyApplication.kt index d640c23d..582f52b7 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/app/MyApplication.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/app/MyApplication.kt @@ -12,4 +12,4 @@ class MyApplication : Application() { // PyTorchImageProcessor.init(applicationContext) TensorFlowImageProcessor.init(applicationContext) } -} \ No newline at end of file +} diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationChannelType.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationChannelType.kt index b6a15a87..5669c1a3 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationChannelType.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationChannelType.kt @@ -9,18 +9,18 @@ enum class NotificationChannelType( val channelId: String, val channelNameResId: Int, // Changed to Int for resource ID val channelDescriptionResId: Int, // Changed to Int for resource ID - val importance: Int + val importance: Int, ) { RENDERING_PROGRESS( "render_progress_channel", R.string.notification_channel_rendering_progress_name, R.string.notification_channel_rendering_progress_description, - NotificationManager.IMPORTANCE_LOW + NotificationManager.IMPORTANCE_LOW, ), RENDERING_COMPLETED( "render_completed_channel", R.string.notification_channel_rendering_completed_name, R.string.notification_channel_rendering_completed_description, - NotificationManager.IMPORTANCE_DEFAULT - ); + NotificationManager.IMPORTANCE_DEFAULT, + ), } diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationFactory.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationFactory.kt index 172628ac..a403e9c0 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationFactory.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/notification/NotificationFactory.kt @@ -19,30 +19,30 @@ class NotificationFactory { NotificationChannelType.entries.forEach { channelType -> // Check if channel already exists to avoid re-creating unnecessarily if (notificationManager.getNotificationChannel(channelType.channelId) == null) { - val channel = NotificationChannel( - channelType.channelId, - context.getString(channelType.channelNameResId), // Use string resource - channelType.importance - ).apply { - description = - context.getString(channelType.channelDescriptionResId) // Use string resource - } + val channel = + NotificationChannel( + channelType.channelId, + context.getString(channelType.channelNameResId), // Use string resource + channelType.importance, + ).apply { + description = + context.getString(channelType.channelDescriptionResId) // Use string resource + } notificationManager.createNotificationChannel(channel) } } } } - fun getRenderProgressNotification( - context: Context - ): NotificationCompat.Builder { + fun getRenderProgressNotification(context: Context): NotificationCompat.Builder { val channelType = NotificationChannelType.RENDERING_PROGRESS // Ensure channel is created. // If you move channel creation to Application class, this explicit call might not be needed here. createNotificationChannels(context) // Or a more optimized way to ensure channels are created - return NotificationCompat.Builder(context, channelType.channelId) + return NotificationCompat + .Builder(context, channelType.channelId) .setSmallIcon(R.drawable.ic_notification_burst) .setContentTitle(context.getString(R.string.notification_render_progress_title)) // Use string resource .setContentText(context.getString(R.string.notification_render_progress_text_starting)) // Use string resource @@ -51,15 +51,14 @@ class NotificationFactory { .setPriority(NotificationCompat.PRIORITY_LOW) } - fun getRenderCompleteNotification( - context: Context - ): NotificationCompat.Builder { + fun getRenderCompleteNotification(context: Context): NotificationCompat.Builder { val channelType = NotificationChannelType.RENDERING_COMPLETED // Ensure channel is created createNotificationChannels(context) // Or a more optimized way to ensure channels are created - return NotificationCompat.Builder(context, channelType.channelId) + return NotificationCompat + .Builder(context, channelType.channelId) .setSmallIcon(R.drawable.ic_notification_burst) .setContentTitle(context.getString(R.string.notification_render_complete_title)) // Use string resource .setContentText(context.getString(R.string.notification_render_complete_text)) // Use string resource diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/presentation/SampleMotionVideo.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/presentation/SampleMotionVideo.kt index 8e5ba35f..a75f8f47 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/presentation/SampleMotionVideo.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/presentation/SampleMotionVideo.kt @@ -14,18 +14,21 @@ import kotlinx.coroutines.runBlocking import java.io.File fun sampleMotionVideo(applicationContext: Context): MotionVideoProducer { - val motionConfig = MotionConfig( - aspectRatio = VideoAspectRatio.Ratio9x16_480, fps = 30 - ) + val motionConfig = + MotionConfig( + aspectRatio = VideoAspectRatio.Ratio9x16_480, + fps = 30, + ) val assetManager = applicationContext.assets val files = assetManager.list(RenaultCar.imageAssetSubFolder) - val motionView: BaseContourMotionView = RenaultCar( - context = applicationContext, - startFrame = 1, - endFrame = files?.size ?: 1 - ) + val motionView: BaseContourMotionView = + RenaultCar( + context = applicationContext, + startFrame = 1, + endFrame = files?.size ?: 1, + ) // val motionView = ContourDevice( // context = applicationContext, startFrame = 1, endFrame = motionConfig.fps * 4 @@ -39,7 +42,7 @@ fun sampleMotionVideo(applicationContext: Context): MotionVideoProducer { try { httpClient.downloadFile( file = file, - url = "https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/3d/be/de/3dbedeeb-4ef4-0b43-d23e-ed7b3ec0c034/mzaf_3312428321786187211.plus.aac.p.m4a" + url = "https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/3d/be/de/3dbedeeb-4ef4-0b43-d23e-ed7b3ec0c034/mzaf_3312428321786187211.plus.aac.p.m4a", ) } catch (e: Exception) { e.printStackTrace() @@ -47,14 +50,15 @@ fun sampleMotionVideo(applicationContext: Context): MotionVideoProducer { } } - val motionAudio = listOf( - MotionAudio( - file = file, - delayFrame = motionView.startFrame, - startFrame = motionView.startFrame, - endFrame = motionView.endFrame + val motionAudio = + listOf( + MotionAudio( + file = file, + delayFrame = motionView.startFrame, + startFrame = motionView.startFrame, + endFrame = motionView.endFrame, + ), ) - ) // val motionView = MotionOpenGlView( // context = applicationContext, @@ -101,9 +105,10 @@ fun sampleMotionVideo(applicationContext: Context): MotionVideoProducer { setBackgroundColor(Color.WHITE) }*/ - return MotionVideoProducer.with( - context = applicationContext, - config = motionConfig, - motionAudio = motionAudio, - ).addMotionViewToSequence(motionView = motionView) -} \ No newline at end of file + return MotionVideoProducer + .with( + context = applicationContext, + config = motionConfig, + motionAudio = motionAudio, + ).addMotionViewToSequence(motionView = motionView) +} diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/ContourDevice.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/ContourDevice.kt index b4d0e8ee..9e35b01e 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/ContourDevice.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/ContourDevice.kt @@ -14,33 +14,38 @@ import com.tejpratapsingh.motionlib.core.extensions.toBitmap import com.tejpratapsingh.motionlib.core.motion.BaseContourMotionView import com.tejpratapsingh.motionlib.ui.custom.text.TypeWriterTextView -class ContourDevice(context: Context, startFrame: Int, endFrame: Int) : - BaseContourMotionView(context, startFrame, endFrame) { +class ContourDevice( + context: Context, + startFrame: Int, + endFrame: Int, +) : BaseContourMotionView(context, startFrame, endFrame) { + private val typeWriterWriterTextView: TypeWriterTextView = + TypeWriterTextView( + context = context, + text = "Hello,\nWelcome to the future", + startFrame = startFrame, + endFrame = endFrame, + ).apply { + setBackgroundColor(Color.WHITE) - private val typeWriterWriterTextView: TypeWriterTextView = TypeWriterTextView( - context = context, - text = "Hello,\nWelcome to the future", - startFrame = startFrame, - endFrame = endFrame - ).apply { - setBackgroundColor(Color.WHITE) - - textView.textSize = 18f - textView.gravity = Gravity.CENTER - } + textView.textSize = 18f + textView.gravity = Gravity.CENTER + } init { typeWriterWriterTextView.layoutBy( - x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, - y = topTo { - parent.top() - }.bottomTo { - parent.bottom() - } + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }.bottomTo { + parent.bottom() + }, ) contourHeightOf { @@ -54,25 +59,26 @@ class ContourDevice(context: Context, startFrame: Int, endFrame: Int) : override fun forFrame(frame: Int): MotionView { super.forFrame(frame) - val backgroundColor: Int = MotionInterpolator.interpolateColorForRange( - Interpolators(Easings.LINEAR), - frame, - Pair(startFrame, endFrame), - Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()) - ) + val backgroundColor: Int = + MotionInterpolator.interpolateColorForRange( + Interpolators(Easings.LINEAR), + frame, + Pair(startFrame, endFrame), + Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()), + ) typeWriterWriterTextView.setBackgroundColor( - backgroundColor + backgroundColor, ) typeWriterWriterTextView.textView.setTextColor( MotionInterpolator.getComplementaryColor( - backgroundColor - ) + backgroundColor, + ), ) return this } override fun getViewBitmap(): Bitmap = this.toBitmap() -} \ No newline at end of file +} diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/MotionVideoContainer.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/MotionVideoContainer.kt index e72420e2..a0866477 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/MotionVideoContainer.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/MotionVideoContainer.kt @@ -20,113 +20,128 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File - -class MotionVideoContainer(context: Context, motionVideoProducer: MotionVideoProducer) : - ContourLayout(context) { - +class MotionVideoContainer( + context: Context, + motionVideoProducer: MotionVideoProducer, +) : ContourLayout(context) { private val TAG = "MotionVideoContainer" - private val toolbar: Toolbar = Toolbar(context).apply { - title = "Video" - subtitle = "Create New" - setBackgroundColor(Color.CYAN) - } - - private val videoPlayer: MotionVideoPlayer = MotionVideoPlayer( - context, motionVideoProducer - ) - - private val exportVideo: Button = Button(context).apply { - text = context.getString(R.string.export_video) + private val toolbar: Toolbar = + Toolbar(context).apply { + title = "Video" + subtitle = "Create New" + setBackgroundColor(Color.CYAN) + } - val scope = CoroutineScope( - Dispatchers.Main + SupervisorJob() + private val videoPlayer: MotionVideoPlayer = + MotionVideoPlayer( + context, + motionVideoProducer, ) - setOnClickListener { - visibility = GONE + private val exportVideo: Button = + Button(context).apply { + text = context.getString(R.string.export_video) - scope.launch { - val uri: Uri = generateVideo( - motionVideoProducer = motionVideoProducer, - progressListener = { progress, bitmap -> - scope.launch { - Log.d(TAG, "Progress: $progress") - videoPlayer.seekBar.progress = progress - } - } + val scope = + CoroutineScope( + Dispatchers.Main + SupervisorJob(), ) - visibility = VISIBLE - val shareIntent = Intent().apply { - action = Intent.ACTION_SEND - type = "video/*" - putExtra(Intent.EXTRA_STREAM, uri) + setOnClickListener { + visibility = GONE + + scope.launch { + val uri: Uri = + generateVideo( + motionVideoProducer = motionVideoProducer, + progressListener = { progress, bitmap -> + scope.launch { + Log.d(TAG, "Progress: $progress") + videoPlayer.seekBar.progress = progress + } + }, + ) + + visibility = VISIBLE + val shareIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "video/*" + putExtra(Intent.EXTRA_STREAM, uri) + + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + context.startActivity(Intent.createChooser(shareIntent, "Share File")) } - - context.startActivity(Intent.createChooser(shareIntent, "Share File")) } } - } init { toolbar.layoutBy( - x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, - y = topTo { - parent.top() - } + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }, ) exportVideo.layoutBy( - x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, - y = bottomTo { - parent.bottom() - } + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + bottomTo { + parent.bottom() + }, ) videoPlayer.layoutBy( - x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, - y = topTo { - toolbar.bottom() - }.bottomTo { - exportVideo.top() - } + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + toolbar.bottom() + }.bottomTo { + exportVideo.top() + }, ) } suspend fun generateVideo( motionVideoProducer: MotionVideoProducer, - progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)? + progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)?, ): Uri = withContext(Dispatchers.IO) { - val fileToShare = motionVideoProducer.produceVideo( - context = context, - outputFile = File.createTempFile( - "out", - ".mp4", - context.filesDir - ), - progressListener = progressListener - ) + val fileToShare = + motionVideoProducer.produceVideo( + context = context, + outputFile = + File.createTempFile( + "out", + ".mp4", + context.filesDir, + ), + progressListener = progressListener, + ) FileProvider.getUriForFile( context, "${context.packageName}.fileprovider", - fileToShare + fileToShare, ) } -} \ No newline at end of file +} diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt index d573fcdc..820b307e 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt @@ -12,16 +12,19 @@ import com.tejpratapsingh.motionlib.core.motion.BaseContourMotionView import java.io.IOException import java.io.InputStream -class RenaultCar(context: Context, startFrame: Int, endFrame: Int) : - BaseContourMotionView(context, startFrame, endFrame) { - +class RenaultCar( + context: Context, + startFrame: Int, + endFrame: Int, +) : BaseContourMotionView(context, startFrame, endFrame) { companion object { const val imageAssetSubFolder = "renault_kiger_bg" } - private val imageView: ImageView = ImageView(context).apply { - scaleType = ImageView.ScaleType.CENTER_INSIDE - } + private val imageView: ImageView = + ImageView(context).apply { + scaleType = ImageView.ScaleType.CENTER_INSIDE + } private val assetManager = context.assets @@ -31,16 +34,18 @@ class RenaultCar(context: Context, startFrame: Int, endFrame: Int) : init { imageView.layoutBy( - x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, - y = topTo { - parent.top() - }.bottomTo { - parent.bottom() - } + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }.bottomTo { + parent.bottom() + }, ) contourHeightOf { @@ -54,15 +59,16 @@ class RenaultCar(context: Context, startFrame: Int, endFrame: Int) : override fun forFrame(frame: Int): MotionView { super.forFrame(frame) - val backgroundColor: Int = MotionInterpolator.interpolateColorForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()) - ) + val backgroundColor: Int = + MotionInterpolator.interpolateColorForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()), + ) setBackgroundColor( - backgroundColor + backgroundColor, ) // Determine which image to show based on the current frame diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/utils/Timer.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/utils/Timer.kt index dc5b8a27..63dfb4a6 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/utils/Timer.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/utils/Timer.kt @@ -6,8 +6,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.launch -class Timer() { - +class Timer { companion object { private const val TAG = "Timer" } @@ -18,7 +17,7 @@ class Timer() { private fun startCoroutineTimer( delayMillis: Long = 0, repeatMillis: Long = 0, - action: () -> Unit + action: () -> Unit, ) = scope.launch(Dispatchers.IO) { delay(delayMillis) if (repeatMillis > 0) { @@ -30,4 +29,4 @@ class Timer() { action() } } -} \ No newline at end of file +} diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/worker/SampleMotionWorker.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/worker/SampleMotionWorker.kt index c6e523d9..c7018f94 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/worker/SampleMotionWorker.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/worker/SampleMotionWorker.kt @@ -29,9 +29,10 @@ import java.net.URLConnection import java.util.Locale import java.util.UUID -class SampleMotionWorker(private val appContext: Context, parameters: WorkerParameters) : - MotionWorker(appContext, parameters) { - +class SampleMotionWorker( + private val appContext: Context, + parameters: WorkerParameters, +) : MotionWorker(appContext, parameters) { private val notificationManager = NotificationManagerCompat.from(appContext) private val progressNotificationBuilder: NotificationCompat.Builder by lazy { @@ -44,50 +45,54 @@ class SampleMotionWorker(private val appContext: Context, parameters: WorkerPara private fun createForegroundInfo( progressNotificationId: Int, - notification: Notification - ): ForegroundInfo { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + notification: Notification, + ): ForegroundInfo = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { ForegroundInfo( progressNotificationId, notification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING, ) } else { ForegroundInfo(progressNotificationId, notification) } - } override suspend fun getForegroundInfo(): ForegroundInfo { // Create the notification for the foreground service - val notification = progressNotificationBuilder - .setContentTitle("Rendering Video...") // Initial title - .setProgress(0, 0, true) // Indeterminate progress initially - .setOngoing(true) - .build() + val notification = + progressNotificationBuilder + .setContentTitle("Rendering Video...") // Initial title + .setProgress(0, 0, true) // Indeterminate progress initially + .setOngoing(true) + .build() return createForegroundInfo(progressNotificationId, notification) } - override fun getMotionVideo(inputData: Data): MotionVideoProducer { - return sampleMotionVideo(appContext) - } + override fun getMotionVideo(inputData: Data): MotionVideoProducer = sampleMotionVideo(appContext) - override fun onProgress(totalFrames: Int, currentProgress: Int, bitmap: Bitmap) { + override fun onProgress( + totalFrames: Int, + currentProgress: Int, + bitmap: Bitmap, + ) { Log.d(TAG, "onProgress: $currentProgress / $totalFrames") val percentage = (currentProgress.toDouble() / totalFrames) * 100 - val progressText = String.format( - Locale.getDefault(), - "%d/%d frames completed", - currentProgress, - totalFrames - ) + val progressText = + String.format( + Locale.getDefault(), + "%d/%d frames completed", + currentProgress, + totalFrames, + ) val contentText = String.format(Locale.getDefault(), "%.0f%%", percentage) - val notification = progressNotificationBuilder - .setProgress(totalFrames, currentProgress, false) - .setSubText(progressText) - .setContentText(contentText) - .build() + val notification = + progressNotificationBuilder + .setProgress(totalFrames, currentProgress, false) + .setSubText(progressText) + .setContentText(contentText) + .build() updateNotification(progressNotificationId, notification) @@ -106,25 +111,24 @@ class SampleMotionWorker(private val appContext: Context, parameters: WorkerPara val intentOpenFile = Intent(Intent.ACTION_VIEW) val pendingOpenFileIntent = createPendingIntentFor(intentOpenFile, videoFile) - val completedNotification = completedNotificationBuilder - .setContentTitle("Render Complete") - .setContentText("Video ready: ${videoFile.name}") - .addAction( - NotificationCompat.Action( - android.R.drawable.ic_menu_share, // Consider using a custom icon - "Share Video", - pendingShareIntent - ) - ) - .addAction( - NotificationCompat.Action( - android.R.drawable.ic_menu_share, // Consider using a custom icon - "Open Video", - pendingOpenFileIntent - ) - ) - .setAutoCancel(true) // Dismiss notification when tapped (if no content intent set) - .build() + val completedNotification = + completedNotificationBuilder + .setContentTitle("Render Complete") + .setContentText("Video ready: ${videoFile.name}") + .addAction( + NotificationCompat.Action( + android.R.drawable.ic_menu_share, // Consider using a custom icon + "Share Video", + pendingShareIntent, + ), + ).addAction( + NotificationCompat.Action( + android.R.drawable.ic_menu_share, // Consider using a custom icon + "Open Video", + pendingOpenFileIntent, + ), + ).setAutoCancel(true) // Dismiss notification when tapped (if no content intent set) + .build() updateNotification(completedNotificationId, completedNotification) } @@ -132,7 +136,10 @@ class SampleMotionWorker(private val appContext: Context, parameters: WorkerPara @Volatile private var lastNotificationUpdateTime = 0L - private fun updateNotification(notificationId: Int, notification: Notification) { + private fun updateNotification( + notificationId: Int, + notification: Notification, + ) { val currentTime = System.currentTimeMillis() if (currentTime - lastNotificationUpdateTime < 500) { return @@ -141,7 +148,7 @@ class SampleMotionWorker(private val appContext: Context, parameters: WorkerPara if (ActivityCompat.checkSelfPermission( appContext, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.POST_NOTIFICATIONS, ) == PackageManager.PERMISSION_GRANTED ) { notificationManager.notify(notificationId, notification) @@ -152,15 +159,19 @@ class SampleMotionWorker(private val appContext: Context, parameters: WorkerPara } } - private fun createPendingIntentFor(intent: Intent, videoFile: File): PendingIntent { - val apkURI: Uri = FileProvider.getUriForFile( - appContext, - "${appContext.packageName}.fileprovider", - videoFile - ) + private fun createPendingIntentFor( + intent: Intent, + videoFile: File, + ): PendingIntent { + val apkURI: Uri = + FileProvider.getUriForFile( + appContext, + "${appContext.packageName}.fileprovider", + videoFile, + ) intent.setDataAndType( apkURI, - URLConnection.guessContentTypeFromName(videoFile.name) + URLConnection.guessContentTypeFromName(videoFile.name), ) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.putExtra(Intent.EXTRA_STREAM, apkURI) @@ -172,7 +183,7 @@ class SampleMotionWorker(private val appContext: Context, parameters: WorkerPara appContext, 0, // requestCode, consider making this unique if you have many such intents intent, - pendingShareIntentFlags + pendingShareIntentFlags, ) } diff --git a/modules/app/src/test/java/com/tejpratapsingh/animator/ExampleUnitTest.kt b/modules/app/src/test/java/com/tejpratapsingh/animator/ExampleUnitTest.kt index 2b5a12c5..03b29910 100644 --- a/modules/app/src/test/java/com/tejpratapsingh/animator/ExampleUnitTest.kt +++ b/modules/app/src/test/java/com/tejpratapsingh/animator/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionAudio.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionAudio.kt index d219356c..aceccb93 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionAudio.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionAudio.kt @@ -12,7 +12,7 @@ import java.io.File */ data class MotionAudio( val file: File, - val startFrame: Int, // trim start frame - val endFrame: Int, // trim end frame - val delayFrame: Int // delay in frames -) \ No newline at end of file + val startFrame: Int, // trim start frame + val endFrame: Int, // trim end frame + val delayFrame: Int, // delay in frames +) diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionConfig.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionConfig.kt index 055b25f9..c387857e 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionConfig.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionConfig.kt @@ -8,11 +8,11 @@ data object MotionConfig { operator fun invoke( aspectRatio: VideoAspectRatio = VideoAspectRatio.Ratio9x16_480, fps: Int = 24, - outputQuality: Int = 100 + outputQuality: Int = 100, ): MotionConfig { this.aspectRatio = aspectRatio this.fps = fps this.outputQuality = outputQuality return this } -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionEffect.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionEffect.kt index 2f63baad..5803f23f 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionEffect.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionEffect.kt @@ -4,4 +4,4 @@ interface MotionEffect : OnMotionFrameListener { val motionView: MotionView val startFrame: Int val endFrame: Int -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionPlugin.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionPlugin.kt index a7f326d1..98b46cc9 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionPlugin.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionPlugin.kt @@ -4,4 +4,4 @@ import android.graphics.Bitmap interface MotionPlugin { fun apply(input: Bitmap): Bitmap -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionView.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionView.kt index a70801de..37dbb21d 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionView.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionView.kt @@ -11,4 +11,4 @@ interface MotionView : OnMotionFrameListener { val effects: List fun getViewBitmap(): Bitmap -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/OnMotionFrameListener.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/OnMotionFrameListener.kt index 60855de9..fef471dd 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/OnMotionFrameListener.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/OnMotionFrameListener.kt @@ -2,4 +2,4 @@ package com.tejpratapsingh.motionlib.core fun interface OnMotionFrameListener { fun forFrame(frame: Int): MotionView -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt index d2c961e9..cd031136 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt @@ -1,42 +1,55 @@ package com.tejpratapsingh.motionlib.core -sealed class VideoAspectRatio(val width: Int, val height: Int, val label: String) { - +sealed class VideoAspectRatio( + val width: Int, + val height: Int, + val label: String, +) { // Square data object Ratio1x1_480 : VideoAspectRatio(480, 480, "1:1 SD") + data object Ratio1x1_720 : VideoAspectRatio(720, 720, "1:1 HD") + data object Ratio1x1_1080 : VideoAspectRatio(1080, 1080, "1:1 Full HD") // Classic TV 4:3 data object Ratio4x3_480 : VideoAspectRatio(640, 480, "4:3 SD") + data object Ratio4x3_576 : VideoAspectRatio(768, 576, "4:3 PAL SD") + data object Ratio4x3_720 : VideoAspectRatio(960, 720, "4:3 HD") // Widescreen 16:9 data object Ratio16x9_480 : VideoAspectRatio(854, 480, "16:9 SD") + data object Ratio16x9_720 : VideoAspectRatio(1280, 720, "16:9 HD") + data object Ratio16x9_1080 : VideoAspectRatio(1920, 1080, "16:9 Full HD") + data object Ratio16x9_1440 : VideoAspectRatio(2560, 1440, "16:9 2K") + data object Ratio16x9_2160 : VideoAspectRatio(3840, 2160, "16:9 4K") // Portrait (for mobile) data object Ratio9x16_480 : VideoAspectRatio(480, 854, "9:16 SD") + data object Ratio9x16_720 : VideoAspectRatio(720, 1280, "9:16 HD") + data object Ratio9x16_1080 : VideoAspectRatio(1080, 1920, "9:16 Full HD") // Cinema wide 21:9 data object Ratio21x9_1080 : VideoAspectRatio(2520, 1080, "21:9 Full HD") + data object Ratio21x9_2160 : VideoAspectRatio(5120, 2160, "21:9 5K") // Custom ratio with any pixel size data class Custom( val customWidth: Int, val customHeight: Int, - val customLabel: String = "Custom" - ) : - VideoAspectRatio(customWidth, customHeight, customLabel) + val customLabel: String = "Custom", + ) : VideoAspectRatio(customWidth, customHeight, customLabel) - fun ratioString(): String = "${width}:${height}" + fun ratioString(): String = "$width:$height" fun aspect(): Float = width.toFloat() / height.toFloat() -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoProducerAdapter.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoProducerAdapter.kt index 2fea8f00..db875b3b 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoProducerAdapter.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoProducerAdapter.kt @@ -12,6 +12,6 @@ interface VideoProducerAdapter { motionAudio: List, totalFrames: Int, outputFile: File, - progressListener: ((Int, Bitmap) -> Unit)? + progressListener: ((Int, Bitmap) -> Unit)?, ): File -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/AssetExtension.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/AssetExtension.kt index bc6ee0ef..851090f1 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/AssetExtension.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/AssetExtension.kt @@ -1,6 +1,5 @@ package com.tejpratapsingh.motionlib.core.extensions - import android.content.Context import android.net.Uri import java.io.File @@ -41,4 +40,4 @@ fun Context.getFileFromAsset(assetFilePath: String): File { } return outFile // For internal use only -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/BitmapExtension.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/BitmapExtension.kt index 304f564e..a23a73ae 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/BitmapExtension.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/BitmapExtension.kt @@ -9,7 +9,7 @@ fun Bitmap.compressToBitmap(quality: Int): Bitmap { val stream = ByteArrayOutputStream() /* - **** reference source developer.android.com *** + **** reference source developer.android.com *** public boolean compress (Bitmap.CompressFormat format, int quality, OutputStream stream) Write a compressed version of the bitmap to the specified outputstream. @@ -37,7 +37,7 @@ fun Bitmap.compressToBitmap(quality: Int): Bitmap { Bitmap.CompressFormat JPEG Bitmap.CompressFormat PNG Bitmap.CompressFormat WEBP - */ + */ // Compress the bitmap with JPEG format and quality 50% this.compress(Bitmap.CompressFormat.JPEG, quality, stream) @@ -46,4 +46,4 @@ fun Bitmap.compressToBitmap(quality: Int): Bitmap { // Finally, return the compressed bitmap return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ContextExtensions.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ContextExtensions.kt index 4ce8da8b..3111ef02 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ContextExtensions.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ContextExtensions.kt @@ -11,17 +11,22 @@ fun Context.loadBitmapsFromDirectory(dirName: String): List { return emptyList() } - return dir.listFiles { file -> - file.extension.lowercase() in listOf("png", "jpg", "jpeg", "webp") - }?.sortedBy { file -> - // Extract digits from filename, default to 0 if no digits found - file.nameWithoutExtension.filter { it.isDigit() }.toIntOrNull() ?: 0 - }?.mapNotNull { file -> - BitmapFactory.decodeFile(file.absolutePath) - } ?: emptyList() + return dir + .listFiles { file -> + file.extension.lowercase() in listOf("png", "jpg", "jpeg", "webp") + }?.sortedBy { file -> + // Extract digits from filename, default to 0 if no digits found + file.nameWithoutExtension.filter { it.isDigit() }.toIntOrNull() ?: 0 + }?.mapNotNull { file -> + BitmapFactory.decodeFile(file.absolutePath) + } ?: emptyList() } -fun Context.saveBitmapToCacheFolder(bitmap: Bitmap, subDirName: String, fileName: String) { +fun Context.saveBitmapToCacheFolder( + bitmap: Bitmap, + subDirName: String, + fileName: String, +) { val cacheSubDir = File(this.cacheDir, subDirName) if (!cacheSubDir.exists()) { cacheSubDir.mkdirs() @@ -30,4 +35,4 @@ fun Context.saveBitmapToCacheFolder(bitmap: Bitmap, subDirName: String, fileName file.outputStream().use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) // Using PNG as per your pattern } -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/KtorExtension.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/KtorExtension.kt index c647a57c..83a64747 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/KtorExtension.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/KtorExtension.kt @@ -19,17 +19,22 @@ import java.io.File import java.io.IOException // For more specific IO exceptions // Custom exception for better error handling if desired -class DownloadException(message: String, cause: Throwable? = null) : IOException(message, cause) +class DownloadException( + message: String, + cause: Throwable? = null, +) : IOException(message, cause) suspend fun HttpClient.downloadFile( file: File, - url: String + url: String, ): File { try { - val response: HttpResponse = this.request { // Changed 'call' to 'response' for clarity - url(url) - method = HttpMethod.Get - } + val response: HttpResponse = + this.request { + // Changed 'call' to 'response' for clarity + url(url) + method = HttpMethod.Get + } if (!response.status.isSuccess()) { throw DownloadException("Error downloading file from $url: HTTP ${response.status}") @@ -51,7 +56,7 @@ suspend fun HttpClient.downloadFile( // Catch specific IOExceptions during file writing throw DownloadException( "Failed to write downloaded file to ${file.path}: ${e.message}", - e + e, ) } } @@ -62,17 +67,16 @@ suspend fun HttpClient.downloadFile( // Catch other potential exceptions (e.g., Ktor client exceptions) throw DownloadException( "An unexpected error occurred during download from $url: ${e.message}", - e + e, ) } } -suspend fun HttpClient.fetchBitmap(url: String): Bitmap? { - return try { +suspend fun HttpClient.fetchBitmap(url: String): Bitmap? = + try { val bytes: ByteArray = get(url).body() BitmapFactory.decodeByteArray(bytes, 0, bytes.size) } catch (e: Exception) { e.printStackTrace() null } -} \ No newline at end of file diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/StringExtension.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/StringExtension.kt index 8343861d..512f9974 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/StringExtension.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/StringExtension.kt @@ -6,4 +6,4 @@ fun String.md5(): String { val bytes = MessageDigest.getInstance("MD5").digest(this.toByteArray()) return bytes.joinToString("") { "%02x".format(it) } -} \ No newline at end of file +} diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ViewExtension.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ViewExtension.kt index 5e32164f..11038265 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ViewExtension.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/extensions/ViewExtension.kt @@ -6,22 +6,22 @@ import android.view.View import androidx.core.graphics.createBitmap fun View.toBitmap(): Bitmap { - //Get the dimensions of the view so we can re-layout the view at its current size - //and create a bitmap of the same size + // Get the dimensions of the view so we can re-layout the view at its current size + // and create a bitmap of the same size val width = this.width val height = this.height val measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY) val measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY) - //Cause the view to re-layout + // Cause the view to re-layout this.measure(measuredWidth, measuredHeight) this.layout(0, 0, this.measuredWidth, this.measuredHeight) - //Create a bitmap backed Canvas to draw the view into + // Create a bitmap backed Canvas to draw the view into val b = createBitmap(width, height) val c = Canvas(b) - //Now that the view is laid out and we have a canvas, ask the view to draw itself into the canvas + // Now that the view is laid out and we have a canvas, ask the view to draw itself into the canvas this.draw(c) return b -} \ No newline at end of file +} diff --git a/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/ExampleUnitTest.kt b/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/ExampleUnitTest.kt index 87bfbd39..a65fea52 100644 --- a/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/ExampleUnitTest.kt +++ b/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/FfmpegVideoProducerAdapter.kt b/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/FfmpegVideoProducerAdapter.kt index 3baef27b..6e457615 100644 --- a/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/FfmpegVideoProducerAdapter.kt +++ b/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/FfmpegVideoProducerAdapter.kt @@ -15,7 +15,6 @@ import java.io.File import java.util.Locale class FfmpegVideoProducerAdapter : VideoProducerAdapter { - companion object { private const val TAG = "FfmpegVideoProducerAdap" } @@ -29,7 +28,7 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { motionAudio: List, totalFrames: Int, outputFile: File, - progressListener: ((Int, Bitmap) -> Unit)? + progressListener: ((Int, Bitmap) -> Unit)?, ): File { if (outputFile.exists()) { outputFile.delete() @@ -44,13 +43,18 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { for (i in 1..totalFrames) { Log.d(TAG, "produceVideo: frame $i") - val frameBitmap: Bitmap = motionComposerView.forFrame(i).getViewBitmap() - .compressToBitmap(motionConfig.outputQuality) + val frameBitmap: Bitmap = + motionComposerView + .forFrame(i) + .getViewBitmap() + .compressToBitmap(motionConfig.outputQuality) // It's good practice to handle potential IOExceptions when saving files try { context.saveBitmapToCacheFolder( - frameBitmap, subDirName, String.format(Locale.getDefault(), "%05d.png", i) + frameBitmap, + subDirName, + String.format(Locale.getDefault(), "%05d.png", i), ) } catch (e: Exception) { Log.e(TAG, "Error saving frame $i: ${e.message}", e) @@ -75,14 +79,15 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { // -r: Output framerate (often the same as input, but can be different) // val query = "-y -framerate ${motionConfig.fps} -start_number 1 -i \"$inputPattern\" -c:v libx264 -pix_fmt yuv420p -r ${motionConfig.fps} \"${outputFile.path}\"" - val query = buildFfmpegCommand( - inputPattern = inputPattern, - fps = motionConfig.fps, - outputFile = outputFile, - audioTracks = motionAudio, - startNumber = 1, - mixAudio = true // Change to false if you want separate audio tracks - ).joinToString(" ") + val query = + buildFfmpegCommand( + inputPattern = inputPattern, + fps = motionConfig.fps, + outputFile = outputFile, + audioTracks = motionAudio, + startNumber = 1, + mixAudio = true, // Change to false if you want separate audio tracks + ).joinToString(" ") Log.d(TAG, "Executing FFmpeg query: $query") val session = FFmpegKit.execute(query) @@ -108,12 +113,12 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { } fun buildFfmpegCommand( - inputPattern: String, // e.g. "/sdcard/frames/frame_%d.png" + inputPattern: String, // e.g. "/sdcard/frames/frame_%d.png" fps: Int, outputFile: File, audioTracks: List, startNumber: Int = 1, - mixAudio: Boolean = true // true = mix tracks, false = keep separate + mixAudio: Boolean = true, // true = mix tracks, false = keep separate ): List { val command = mutableListOf() @@ -121,10 +126,13 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { command.addAll( listOf( "-y", - "-framerate", fps.toString(), - "-start_number", startNumber.toString(), - "-i", inputPattern - ) + "-framerate", + fps.toString(), + "-start_number", + startNumber.toString(), + "-i", + inputPattern, + ), ) // Add audio inputs @@ -142,9 +150,9 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { val label = "a${index + 1}" filterParts.add( - "[${index + 1}:a]atrim=start=${startSec}:end=${endSec}," + - "asetpts=PTS-STARTPTS," + - "adelay=${delayMs}|${delayMs}[$label]" + "[${index + 1}:a]atrim=start=$startSec:end=$endSec," + + "asetpts=PTS-STARTPTS," + + "adelay=$delayMs|$delayMs[$label]", ) } @@ -167,13 +175,13 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { "-r", fps.toString(), "-shortest", - outputFile.absolutePath - ) + outputFile.absolutePath, + ), ) } else { // Keep tracks separate, no filter_complex command.addAll( - listOf("-c:v", "libx264", "-pix_fmt", "yuv420p", "-r", fps.toString()) + listOf("-c:v", "libx264", "-pix_fmt", "yuv420p", "-r", fps.toString()), ) // Map video and each audio @@ -189,7 +197,8 @@ class FfmpegVideoProducerAdapter : VideoProducerAdapter { return command } - fun frameToSeconds(frame: Int, fps: Int): Double { - return frame.toDouble() / fps.toDouble() - } + fun frameToSeconds( + frame: Int, + fps: Int, + ): Double = frame.toDouble() / fps.toDouble() } diff --git a/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/utils/FFMpegExtensions.kt b/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/utils/FFMpegExtensions.kt index fb118ce1..c21a3bbd 100644 --- a/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/utils/FFMpegExtensions.kt +++ b/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/utils/FFMpegExtensions.kt @@ -6,7 +6,9 @@ import com.arthenica.ffmpegkit.ReturnCode import java.io.File fun extractFramesFromVideo( - context: Context, videoFile: File, outputDirName: String = "frames" + context: Context, + videoFile: File, + outputDirName: String = "frames", ): String { val outputDir = File(context.cacheDir, outputDirName) if (!outputDir.exists()) outputDir.mkdirs() @@ -28,4 +30,4 @@ fun extractFramesFromVideo( } return outputDirName -} \ No newline at end of file +} diff --git a/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/video/FFMpegVideoFrameView.kt b/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/video/FFMpegVideoFrameView.kt index 4cf42b35..4e07df2a 100644 --- a/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/video/FFMpegVideoFrameView.kt +++ b/modules/ffmpeg-motion-ext/src/main/java/com/tejpratapsingh/motionlib/ffmpeg/video/FFMpegVideoFrameView.kt @@ -16,21 +16,25 @@ class FFMpegVideoFrameView( val videoFile: File, override val startFrame: Int, override val endFrame: Int, - override val loop: Pair = Pair(0, 0) -) : FrameLayout(context), MotionView { - + override val loop: Pair = Pair(0, 0), +) : FrameLayout(context), + MotionView { override val effects: List = emptyList() - val imageView = ImageView(context).apply { - scaleType = ImageView.ScaleType.CENTER_CROP - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - } - - private val videoBitmaps = extractFramesFromVideo( - context = context, videoFile = videoFile, outputDirName = videoFile.name.md5() - ).let { - context.loadBitmapsFromDirectory(it) - } + val imageView = + ImageView(context).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + } + + private val videoBitmaps = + extractFramesFromVideo( + context = context, + videoFile = videoFile, + outputDirName = videoFile.name.md5(), + ).let { + context.loadBitmapsFromDirectory(it) + } init { addView(imageView) @@ -41,10 +45,10 @@ class FFMpegVideoFrameView( override fun forFrame(frame: Int): MotionView { currentFrameBitmap = videoBitmaps.getOrNull(frame - startFrame) ?: videoBitmaps.last() imageView.setImageBitmap( - currentFrameBitmap + currentFrameBitmap, ) return this } override fun getViewBitmap(): Bitmap = currentFrameBitmap -} \ No newline at end of file +} diff --git a/modules/ffmpeg-motion-ext/src/test/java/com/tejpratapsingh/motionlib/ffmpeg/ExampleUnitTest.kt b/modules/ffmpeg-motion-ext/src/test/java/com/tejpratapsingh/motionlib/ffmpeg/ExampleUnitTest.kt index bb543cbc..4c79cd90 100644 --- a/modules/ffmpeg-motion-ext/src/test/java/com/tejpratapsingh/motionlib/ffmpeg/ExampleUnitTest.kt +++ b/modules/ffmpeg-motion-ext/src/test/java/com/tejpratapsingh/motionlib/ffmpeg/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ivi_demo/ExampleInstrumentedTest.kt b/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ivi_demo/ExampleInstrumentedTest.kt index 6834f048..3333ff22 100644 --- a/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ivi_demo/ExampleInstrumentedTest.kt +++ b/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ivi_demo/ExampleInstrumentedTest.kt @@ -19,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.tejpratapsingh.ivi_demo", appContext.packageName) } -} \ No newline at end of file +} diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/MainActivity.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/MainActivity.kt index 696db9d2..d0354e97 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/MainActivity.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/MainActivity.kt @@ -11,23 +11,25 @@ import com.tejpratapsingh.motionlib.core.motion.BaseContourMotionView import com.tejpratapsingh.motionlib.core.motion.MotionVideoProducer class MainActivity : PreviewActivity() { - val video by lazy { - MotionVideoProducer.with( - context = applicationContext, - config = motionConfig, - ).addMotionViewToSequence(motionView = motionView) + MotionVideoProducer + .with( + context = applicationContext, + config = motionConfig, + ).addMotionViewToSequence(motionView = motionView) } - val motionConfig = MotionConfig( - aspectRatio = VideoAspectRatio.Ratio16x9_480, fps = 30 - ) + val motionConfig = + MotionConfig( + aspectRatio = VideoAspectRatio.Ratio16x9_480, + fps = 30, + ) val motionView: BaseContourMotionView by lazy { RenaultCar( context = applicationContext, startFrame = 1, - endFrame = 72 + endFrame = 72, ) } @@ -44,11 +46,9 @@ class MainActivity : PreviewActivity() { motionVideoPlayer.seekBar.progress = newProgress video.motionComposerView.forFrame(newProgress) }, - sensitivity = 5f + sensitivity = 5f, ) } - override fun getMotionVideo(): MotionVideoProducer { - return video - } -} \ No newline at end of file + override fun getMotionVideo(): MotionVideoProducer = video +} diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/extension/ViewExtensions.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/extension/ViewExtensions.kt index d3f27159..5b88c362 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/extension/ViewExtensions.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/extension/ViewExtensions.kt @@ -11,7 +11,7 @@ fun View.enableSwipeSeek( maxProgress: Int, initialProgress: () -> Int, onProgressChanged: (Int) -> Unit, - sensitivity: Float = 2f + sensitivity: Float = 2f, ) { var lastX = 0f var progressOnStart = 0 @@ -32,7 +32,9 @@ fun View.enableSwipeSeek( true } - else -> false + else -> { + false + } } } } @@ -42,7 +44,7 @@ fun View.enableSwipeSeekReverse( maxProgress: Int, initialProgress: () -> Int, onProgressChanged: (Int) -> Unit, - sensitivity: Float = 2f + sensitivity: Float = 2f, ) { var lastX = 0f var progressOnStart = 0 @@ -63,7 +65,9 @@ fun View.enableSwipeSeekReverse( true } - else -> false + else -> { + false + } } } } diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/RenaultCar.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/RenaultCar.kt index 06a1b2b6..69acfae8 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/RenaultCar.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/RenaultCar.kt @@ -14,47 +14,61 @@ import java.io.InputStream import java.util.Locale import kotlin.math.min -class RenaultCar(context: Context, startFrame: Int, endFrame: Int) : - BaseContourMotionView(context, startFrame, endFrame) { - +class RenaultCar( + context: Context, + startFrame: Int, + endFrame: Int, +) : BaseContourMotionView(context, startFrame, endFrame) { companion object { private const val TAG = "RenaultCar" const val imageAssetSubFolder = "renault_kiger_bg" const val roadAssetSubFolder = "road" } - private val imageViewBg: ImageView = ImageView(context).apply { - scaleType = ImageView.ScaleType.FIT_XY - } + private val imageViewBg: ImageView = + ImageView(context).apply { + scaleType = ImageView.ScaleType.FIT_XY + } - private val imageView: ImageView = ImageView(context).apply { - scaleType = ImageView.ScaleType.CENTER_INSIDE - } + private val imageView: ImageView = + ImageView(context).apply { + scaleType = ImageView.ScaleType.CENTER_INSIDE + } private val assetManager = context.assets private val files = assetManager.list(imageAssetSubFolder) private val roadFiles = assetManager.list(roadAssetSubFolder) init { - imageViewBg.layoutBy(x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, y = topTo { - parent.top() - }.bottomTo { - parent.bottom() - }) - - imageView.layoutBy(x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, y = topTo { - parent.top() - }.bottomTo { - parent.bottom() - }) + imageViewBg.layoutBy( + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }.bottomTo { + parent.bottom() + }, + ) + + imageView.layoutBy( + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }.bottomTo { + parent.bottom() + }, + ) contourHeightOf { MotionConfig.aspectRatio.height.toYInt() @@ -67,23 +81,25 @@ class RenaultCar(context: Context, startFrame: Int, endFrame: Int) : override fun forFrame(frame: Int): MotionView { super.forFrame(frame) - val backgroundColor: Int = MotionInterpolator.interpolateColorForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()) - ) + val backgroundColor: Int = + MotionInterpolator.interpolateColorForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()), + ) setBackgroundColor( - backgroundColor + backgroundColor, ) - val scaleInterpolator = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.BACK_IN_OUT), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(0.5f, 1.0f) - ) + val scaleInterpolator = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.BACK_IN_OUT), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(0.5f, 1.0f), + ) imageView.scaleX = scaleInterpolator imageView.scaleY = scaleInterpolator @@ -112,12 +128,13 @@ class RenaultCar(context: Context, startFrame: Int, endFrame: Int) : // Log.e(TAG, "Error loading image from asset: $road", e) // } - val imageName = String.format( - Locale.getDefault(), - "%s/%d.png", - imageAssetSubFolder, - min(frame, (files?.size ?: 1) - 1) - ) + val imageName = + String.format( + Locale.getDefault(), + "%s/%d.png", + imageAssetSubFolder, + min(frame, (files?.size ?: 1) - 1), + ) try { val inputStream: InputStream = assetManager.open(imageName) diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/Road.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/Road.kt index f261cebf..e08df4e3 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/Road.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/Road.kt @@ -10,30 +10,38 @@ import com.tejpratapsingh.motionlib.core.motion.BaseContourMotionView import java.io.IOException import java.io.InputStream -class Road(context: Context, startFrame: Int, endFrame: Int) : - BaseContourMotionView(context, startFrame, endFrame) { - +class Road( + context: Context, + startFrame: Int, + endFrame: Int, +) : BaseContourMotionView(context, startFrame, endFrame) { companion object { private const val TAG = "Road" const val imageAssetSubFolder = "road" } - private val imageView: ImageView = ImageView(context).apply { - scaleType = ImageView.ScaleType.CENTER_INSIDE - } + private val imageView: ImageView = + ImageView(context).apply { + scaleType = ImageView.ScaleType.CENTER_INSIDE + } private val assetManager = context.assets init { - imageView.layoutBy(x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, y = topTo { - parent.top() - }.bottomTo { - parent.bottom() - }) + imageView.layoutBy( + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }.bottomTo { + parent.bottom() + }, + ) contourHeightOf { MotionConfig.aspectRatio.height.toYInt() diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/sequence/RenaultSequence.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/sequence/RenaultSequence.kt index 93baa0dc..18b04e12 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/sequence/RenaultSequence.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/sequence/RenaultSequence.kt @@ -8,28 +8,33 @@ import com.tejpratapsingh.motionlib.core.motion.BaseContourMotionView import com.tejpratapsingh.motionlib.core.motion.MotionVideoProducer fun sampleMotionVideo(applicationContext: Context): MotionVideoProducer { - val motionConfig = MotionConfig( - aspectRatio = VideoAspectRatio.Ratio9x16_480, fps = 30 - ) + val motionConfig = + MotionConfig( + aspectRatio = VideoAspectRatio.Ratio9x16_480, + fps = 30, + ) val assetManager = applicationContext.assets val files = assetManager.list(RenaultCar.imageAssetSubFolder) - val motionView: BaseContourMotionView = RenaultCar( - context = applicationContext, - startFrame = 1, - endFrame = files?.size ?: 1 - ) + val motionView: BaseContourMotionView = + RenaultCar( + context = applicationContext, + startFrame = 1, + endFrame = files?.size ?: 1, + ) - val motionView2: BaseContourMotionView = RenaultCar( - context = applicationContext, - startFrame = 1, - endFrame = 55000 - ) + val motionView2: BaseContourMotionView = + RenaultCar( + context = applicationContext, + startFrame = 1, + endFrame = 55000, + ) - return MotionVideoProducer.with( - context = applicationContext, - config = motionConfig, - ).addMotionViewToSequence(motionView = motionView) + return MotionVideoProducer + .with( + context = applicationContext, + config = motionConfig, + ).addMotionViewToSequence(motionView = motionView) .addMotionViewToSequence(motionView = motionView2) -} \ No newline at end of file +} diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/view/TrapezoidImageView.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/view/TrapezoidImageView.kt index ca84f1f6..7298f227 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/view/TrapezoidImageView.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/view/TrapezoidImageView.kt @@ -9,45 +9,56 @@ import android.util.AttributeSet import android.view.View import androidx.core.graphics.withMatrix -class TrapezoidImageView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null -) : View(context, attrs) { +class TrapezoidImageView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + ) : View(context, attrs) { + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + private var bitmap: Bitmap? = null - private val paint = Paint(Paint.ANTI_ALIAS_FLAG) - private var bitmap: Bitmap? = null - - fun setImageBitmap(bmp: Bitmap) { - bitmap = bmp - invalidate() - } + fun setImageBitmap(bmp: Bitmap) { + bitmap = bmp + invalidate() + } - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - val bmp = bitmap ?: return + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + val bmp = bitmap ?: return - val w = bmp.width.toFloat() - val h = bmp.height.toFloat() + val w = bmp.width.toFloat() + val h = bmp.height.toFloat() - // Destination trapezoid (isosceles) - val topInset = w * 0.3f // how much shorter the top is - val src = floatArrayOf( - 0f, 0f, // top-left - w, 0f, // top-right - 0f, h, // bottom-left - w, h // bottom-right - ) - val dst = floatArrayOf( - topInset, 0f, // top-left - w - topInset, 0f, // top-right - 0f, h, // bottom-left - w, h // bottom-right - ) + // Destination trapezoid (isosceles) + val topInset = w * 0.3f // how much shorter the top is + val src = + floatArrayOf( + 0f, + 0f, // top-left + w, + 0f, // top-right + 0f, + h, // bottom-left + w, + h, // bottom-right + ) + val dst = + floatArrayOf( + topInset, + 0f, // top-left + w - topInset, + 0f, // top-right + 0f, + h, // bottom-left + w, + h, // bottom-right + ) - val matrix = Matrix() - matrix.setPolyToPoly(src, 0, dst, 0, 4) - canvas.withMatrix(matrix) { - drawBitmap(bmp, 0f, 0f, paint) + val matrix = Matrix() + matrix.setPolyToPoly(src, 0, dst, 0, 4) + canvas.withMatrix(matrix) { + drawBitmap(bmp, 0f, 0f, paint) + } } } -} diff --git a/modules/ivi-demo/src/test/java/com/tejpratapsingh/ivi_demo/ExampleUnitTest.kt b/modules/ivi-demo/src/test/java/com/tejpratapsingh/ivi_demo/ExampleUnitTest.kt index 3b559ec3..a3032286 100644 --- a/modules/ivi-demo/src/test/java/com/tejpratapsingh/ivi_demo/ExampleUnitTest.kt +++ b/modules/ivi-demo/src/test/java/com/tejpratapsingh/ivi_demo/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/FileExtension.kt b/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/FileExtension.kt index 721416d0..41dc33dc 100644 --- a/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/FileExtension.kt +++ b/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/FileExtension.kt @@ -16,9 +16,7 @@ fun File.getVideoMetadata(): DemuxerTrackMeta { return vt.meta } -fun File.getSingleFrameFromVideo(frameNumber: Int): Bitmap { - return AndroidFrameGrab.getFrame(this, frameNumber) -} +fun File.getSingleFrameFromVideo(frameNumber: Int): Bitmap = AndroidFrameGrab.getFrame(this, frameNumber) /** * Should not use this, it will require a lot of memory @@ -31,4 +29,4 @@ fun File.getAllFramesFromFile(): MutableList { bitmapList.add(AndroidUtil.toBitmap(picture)) } return bitmapList -} \ No newline at end of file +} diff --git a/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/JCodecVideoProducerAdapter.kt b/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/JCodecVideoProducerAdapter.kt index 30a64eb5..5e27b69a 100644 --- a/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/JCodecVideoProducerAdapter.kt +++ b/modules/jcodec-motion-ext/src/main/java/com/tejpratapsingh/motionlib/jcodec/JCodecVideoProducerAdapter.kt @@ -12,7 +12,6 @@ import org.jcodec.api.android.AndroidSequenceEncoder import java.io.File class JCodecVideoProducerAdapter : VideoProducerAdapter { - companion object { private const val TAG = "JCodecVideoProducerAdap" } @@ -24,7 +23,7 @@ class JCodecVideoProducerAdapter : VideoProducerAdapter { motionAudio: List, totalFrames: Int, outputFile: File, - progressListener: ((Int, Bitmap) -> Unit)? + progressListener: ((Int, Bitmap) -> Unit)?, ): File { if (outputFile.exists()) { outputFile.delete() @@ -34,7 +33,9 @@ class JCodecVideoProducerAdapter : VideoProducerAdapter { for (i in 1..totalFrames) { Log.d(TAG, "produceVideo: frame $i") val frameBitmap: Bitmap = - motionComposerView.forFrame(i).getViewBitmap() + motionComposerView + .forFrame(i) + .getViewBitmap() .compressToBitmap(motionConfig.outputQuality) encoder.encodeImage(frameBitmap) @@ -51,4 +52,4 @@ class JCodecVideoProducerAdapter : VideoProducerAdapter { return outputFile } -} \ No newline at end of file +} diff --git a/modules/jcodec-motion-ext/src/test/java/com/tejpratapsingh/motionlib/jcodec/ExampleUnitTest.kt b/modules/jcodec-motion-ext/src/test/java/com/tejpratapsingh/motionlib/jcodec/ExampleUnitTest.kt index 94e64e0b..1dee2512 100644 --- a/modules/jcodec-motion-ext/src/test/java/com/tejpratapsingh/motionlib/jcodec/ExampleUnitTest.kt +++ b/modules/jcodec-motion-ext/src/test/java/com/tejpratapsingh/motionlib/jcodec/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/ExampleInstrumentedTest.kt b/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/ExampleInstrumentedTest.kt index 31ac6088..5577ea08 100644 --- a/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/ExampleInstrumentedTest.kt +++ b/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/ExampleInstrumentedTest.kt @@ -19,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.tejpratapsingh.lyricsmaker", appContext.packageName) } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/AlbumArtFetcher.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/AlbumArtFetcher.kt index 10ed83cf..98ab32cd 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/AlbumArtFetcher.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/AlbumArtFetcher.kt @@ -16,61 +16,68 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json object AlbumArtFetcher { - private val client = HttpClient(CIO) { - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - isLenient = true - }) - } - defaultRequest { - url { - protocol = URLProtocol.HTTPS - host = "musicbrainz.org" + private val client = + HttpClient(CIO) { + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + isLenient = true + }, + ) + } + defaultRequest { + url { + protocol = URLProtocol.HTTPS + host = "musicbrainz.org" + } + headers.append( + HttpHeaders.UserAgent, + "AlbumArtFetcher/1.0 (lyrics@tejpratapsingh.com)", + ) } - headers.append( - HttpHeaders.UserAgent, - "AlbumArtFetcher/1.0 (lyrics@tejpratapsingh.com)" - ) } - } - enum class CoverSize(val suffix: String) { - ORIGINAL(""), // full resolution + enum class CoverSize( + val suffix: String, + ) { + ORIGINAL(""), // full resolution SMALL("-250"), MEDIUM("-500"), - LARGE("-1200") + LARGE("-1200"), } suspend fun fetchAlbumArtUrl( trackName: String, artistName: String, - size: CoverSize = CoverSize.SMALL + size: CoverSize = CoverSize.SMALL, ): String? { - val response: MusicBrainzResponse = client.get("/ws/2/recording") { - parameter("query", "recording:\"$trackName\" AND artist:\"$artistName\"") - parameter("fmt", "json") - }.body() + val response: MusicBrainzResponse = + client + .get("/ws/2/recording") { + parameter("query", "recording:\"$trackName\" AND artist:\"$artistName\"") + parameter("fmt", "json") + }.body() - val releaseId = response.recordings - .firstOrNull() - ?.releases - ?.firstOrNull() - ?.id ?: return null + val releaseId = + response.recordings + .firstOrNull() + ?.releases + ?.firstOrNull() + ?.id ?: return null // Add size suffix return "https://coverartarchive.org/release/$releaseId/front${size.suffix}" } - suspend fun fetchAlbumArtBitmap(url: String): Bitmap? { - return try { + suspend fun fetchAlbumArtBitmap(url: String): Bitmap? = + try { val bytes: ByteArray = client.get(url).body() BitmapFactory.decodeByteArray(bytes, 0, bytes.size) } catch (e: Exception) { e.printStackTrace() null } - } fun close() { client.close() @@ -79,15 +86,15 @@ object AlbumArtFetcher { @Serializable data class MusicBrainzResponse( - val recordings: List = emptyList() + val recordings: List = emptyList(), ) @Serializable data class Recording( - val releases: List = emptyList() + val releases: List = emptyList(), ) @Serializable data class Release( - val id: String + val id: String, ) diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/LrcLibClient.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/LrcLibClient.kt index 852d9265..f7a2ad27 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/LrcLibClient.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/client/LrcLibClient.kt @@ -13,30 +13,34 @@ import kotlinx.serialization.json.Json class LrcLibClient( private val baseUrl: String = "https://lrclib.net/api", - private val apiKey: String? = null + private val apiKey: String? = null, ) { - private val client = HttpClient(CIO) { - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - }) + private val client = + HttpClient(CIO) { + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + }, + ) + } } - } - suspend fun searchLyrics(query: SearchQuery): List { - return client.get("$baseUrl/search") { - parameter("q", query.searchTerm) - }.body() - } + suspend fun searchLyrics(query: SearchQuery): List = + client + .get("$baseUrl/search") { + parameter("q", query.searchTerm) + }.body() suspend fun getLyrics(query: LyricsQuery): LyricsResponse? { - val response = client.get("$baseUrl/get") { - query.id?.let { parameter("id", it) } - query.trackName?.let { parameter("track_name", it) } - query.artistName?.let { parameter("artist_name", it) } - query.albumName?.let { parameter("album_name", it) } - query.duration?.let { parameter("duration", it) } - } + val response = + client.get("$baseUrl/get") { + query.id?.let { parameter("id", it) } + query.trackName?.let { parameter("track_name", it) } + query.artistName?.let { parameter("artist_name", it) } + query.albumName?.let { parameter("album_name", it) } + query.duration?.let { parameter("duration", it) } + } return if (response.status.value == 404) null else response.body() } diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsQuery.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsQuery.kt index 860dacc0..1e95f1b9 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsQuery.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsQuery.kt @@ -8,5 +8,5 @@ data class LyricsQuery( val trackName: String? = null, val artistName: String? = null, val albumName: String? = null, - val duration: Int? = null -) \ No newline at end of file + val duration: Int? = null, +) diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsResponse.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsResponse.kt index c0963fee..a530dc2a 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsResponse.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/LyricsResponse.kt @@ -18,13 +18,12 @@ data class LyricsResponse( val plainLyrics: String? = null, val syncedLyrics: String? = null, ) : Parcelable { - fun getLyrics(): String { - return if (syncedLyrics.isNullOrEmpty()) { + fun getLyrics(): String = + if (syncedLyrics.isNullOrEmpty()) { "[0:00.00] No Lyrics Found" } else { syncedLyrics } - } fun getReadableDuration(): String { val totalSeconds = (duration ?: 0f).toInt() diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/SearchQuery.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/SearchQuery.kt index cadf1170..f4b942c6 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/SearchQuery.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/api/model/SearchQuery.kt @@ -3,4 +3,6 @@ package com.tejpratapsingh.lyricsmaker.data.api.model import kotlinx.serialization.Serializable @Serializable -data class SearchQuery(val searchTerm: String) +data class SearchQuery( + val searchTerm: String, +) diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt index e8a039d8..803cb0b2 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt @@ -2,10 +2,9 @@ package com.tejpratapsingh.lyricsmaker.data.lrc object LrcHelper { fun getSyncedLyrics( - lrcContent: String, fps: Int - ): List { - return getSyncedLyricsWithFrameOffset(lrcContent, fps) - } + lrcContent: String, + fps: Int, + ): List = getSyncedLyricsWithFrameOffset(lrcContent, fps) /** * Parses the raw LRC string into synced lyric frames. @@ -14,18 +13,22 @@ object LrcHelper { * @param parser Custom parser if needed */ fun getSyncedLyricsWithFrameOffset( - lrcContent: String, fps: Int, offsetFrames: Int = 0, + lrcContent: String, + fps: Int, + offsetFrames: Int = 0, parser: LrcParser = LrcParser(), ): List { val parsedResult = parser.parse(lrcContent) - return parsedResult.map { - val frame = - ((it.time / (1000.0 / fps)).toInt() - offsetFrames).coerceAtLeast(0) // avoid negative frames - SyncedLyricFrame( - frame = frame, text = it.text - ) - }.sortedBy { it.frame } + return parsedResult + .map { + val frame = + ((it.time / (1000.0 / fps)).toInt() - offsetFrames).coerceAtLeast(0) // avoid negative frames + SyncedLyricFrame( + frame = frame, + text = it.text, + ) + }.sortedBy { it.frame } } /** @@ -33,7 +36,9 @@ object LrcHelper { * Converts ms to frames before shifting. */ fun getSyncedLyricsWithMsOffset( - lrcContent: String, fps: Int, offsetMs: Long = 0L + lrcContent: String, + fps: Int, + offsetMs: Long = 0L, ): List { val offsetFrames = (offsetMs / (1000.0 / fps)).toInt() return getSyncedLyricsWithFrameOffset(lrcContent, fps, offsetFrames) @@ -43,17 +48,15 @@ object LrcHelper { * Find the current lyric line for a given frame */ fun getCurrentLyric( - lyrics: List, currentFrame: Int - ): SyncedLyricFrame? { - return lyrics.lastOrNull { it.frame <= currentFrame } - } + lyrics: List, + currentFrame: Int, + ): SyncedLyricFrame? = lyrics.lastOrNull { it.frame <= currentFrame } /** * Find the next lyric line for a given frame */ fun getNextLyric( - lyrics: List, currentFrame: Int - ): SyncedLyricFrame? { - return lyrics.firstOrNull { it.frame > currentFrame } - } -} \ No newline at end of file + lyrics: List, + currentFrame: Int, + ): SyncedLyricFrame? = lyrics.firstOrNull { it.frame > currentFrame } +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcLine.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcLine.kt index a24107d6..a9731074 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcLine.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcLine.kt @@ -2,5 +2,5 @@ package com.tejpratapsingh.lyricsmaker.data.lrc data class LrcLine( val time: Long, // in ms - val text: String -) \ No newline at end of file + val text: String, +) diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcParser.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcParser.kt index 4d14f2ad..4e163c9f 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcParser.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcParser.kt @@ -13,7 +13,11 @@ class LrcParser { matches.forEach { match -> val min = match.groupValues[1].toInt() val sec = match.groupValues[2].toInt() - val ms = match.groupValues.getOrNull(3)?.padEnd(3, '0')?.toIntOrNull() ?: 0 + val ms = + match.groupValues + .getOrNull(3) + ?.padEnd(3, '0') + ?.toIntOrNull() ?: 0 val timeMs = (min * 60 * 1000 + sec * 1000 + ms).toLong() lines.add(LrcLine(timeMs, lyricText)) @@ -22,4 +26,4 @@ class LrcParser { return lines.sortedBy { it.time } } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt index 75eebf5e..4882283f 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt @@ -8,5 +8,5 @@ import kotlinx.serialization.Serializable @Parcelize data class SyncedLyricFrame( val frame: Int, - val text: String + val text: String, ) : Parcelable diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/store/RecentSearchHelper.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/store/RecentSearchHelper.kt index b5ae2b6b..9e866aca 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/store/RecentSearchHelper.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/store/RecentSearchHelper.kt @@ -8,13 +8,18 @@ object RecentSearchHelper { private const val PREF_NAME = "recent_searches" private const val KEY_SEARCHES = "searches" - fun saveSearch(context: Context, query: String) { + fun saveSearch( + context: Context, + query: String, + ) { val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) val searches = getSearches(context).toMutableList() searches.remove(query) searches.add(0, query) - if (searches.size > 10) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - searches.removeLast() + if (searches.size > 10) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + searches.removeLast() + } } prefs.edit { putStringSet(KEY_SEARCHES, searches.toSet()) } } @@ -23,4 +28,4 @@ object RecentSearchHelper { val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) return prefs.getStringSet(KEY_SEARCHES, emptySet())?.toList() ?: emptyList() } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/domain/ListExtensions.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/domain/ListExtensions.kt index 109a30ee..7f7610c9 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/domain/ListExtensions.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/domain/ListExtensions.kt @@ -1,5 +1,3 @@ package com.tejpratapsingh.lyricsmaker.domain -fun List.ensureArrayList(): ArrayList { - return this as? ArrayList ?: ArrayList(this) -} \ No newline at end of file +fun List.ensureArrayList(): ArrayList = this as? ArrayList ?: ArrayList(this) diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/LyricsActivity.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/LyricsActivity.kt index a7654372..fc777818 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/LyricsActivity.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/LyricsActivity.kt @@ -14,9 +14,7 @@ import com.tejpratapsingh.motionlib.activities.PreviewActivity import com.tejpratapsingh.motionlib.core.MotionConfig import com.tejpratapsingh.motionlib.core.motion.MotionVideoProducer - class LyricsActivity : PreviewActivity() { - companion object { private const val TAG = "LyricsActivity" @@ -27,14 +25,15 @@ class LyricsActivity : PreviewActivity() { context: Context, song: String, lyrics: ArrayList, - socialMeta: SocialMeta? = null + socialMeta: SocialMeta? = null, ) { context.startActivity( Intent(context, LyricsActivity::class.java).also { it.putExtra(SONG, song) it.putExtra(ShareReceiverActivity.EXTRA_METADATA, socialMeta) it.putParcelableArrayListExtra(LYRICS, lyrics) - }) + }, + ) } } @@ -42,13 +41,14 @@ class LyricsActivity : PreviewActivity() { get() = intent.getStringExtra(SONG) ?: "" private val lyrics: List - get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableArrayListExtra(LYRICS, SyncedLyricFrame::class.java)?.toList() - ?: emptyList() - } else { - @Suppress("DEPRECATION") - intent.getParcelableArrayListExtra(LYRICS) ?: emptyList() - } + get() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayListExtra(LYRICS, SyncedLyricFrame::class.java)?.toList() + ?: emptyList() + } else { + @Suppress("DEPRECATION") + intent.getParcelableArrayListExtra(LYRICS) ?: emptyList() + } private val socialMeta get() = ShareReceiverActivity.readMetadataFromIntent(intent) @@ -58,7 +58,7 @@ class LyricsActivity : PreviewActivity() { applicationContext = applicationContext, song = song, lyrics = lyrics, - image = socialMeta?.image + image = socialMeta?.image, ) } @@ -68,26 +68,27 @@ class LyricsActivity : PreviewActivity() { val start = lyrics.minBy { it.frame }.frame val end = lyrics.maxBy { it.frame }.frame - MaterialAlertDialogBuilder(this).setTitle("Lyrics").setMessage( - """ + MaterialAlertDialogBuilder(this) + .setTitle("Lyrics") + .setMessage( + """ Rendering video for \"$song\" with ${lyrics.size} lines of lyrics. Start Frame: $start End Frame: ${getMotionVideo().totalFrames} Duration: ${(end - start)} frames (${(end - start) / MotionConfig.fps} seconds) - """.trimIndent() - ).setPositiveButton("OK") { dialog, _ -> - LyricsMotionWorker.startWork( - context = applicationContext, - song = song, - lyrics = lyrics, - image = socialMeta?.image - ) - }.setNegativeButton("Cancel") { dialog, _ -> - dialog.dismiss() - }.setCancelable(false).show() + """.trimIndent(), + ).setPositiveButton("OK") { dialog, _ -> + LyricsMotionWorker.startWork( + context = applicationContext, + song = song, + lyrics = lyrics, + image = socialMeta?.image, + ) + }.setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + }.setCancelable(false) + .show() } - override fun getMotionVideo(): MotionVideoProducer { - return video - } -} \ No newline at end of file + override fun getMotionVideo(): MotionVideoProducer = video +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt index 94a95018..7536f3c3 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt @@ -22,7 +22,6 @@ import com.tejpratapsingh.motion.metadataextractor.ShareReceiverActivity import kotlinx.coroutines.launch class SearchActivity : ComponentActivity() { - private val lyricsViewModel: LyricsViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -32,11 +31,14 @@ class SearchActivity : ComponentActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ActivityCompat.checkSelfPermission( - this, Manifest.permission.POST_NOTIFICATIONS + this, + Manifest.permission.POST_NOTIFICATIONS, ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( - this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0 + this, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + 0, ) } } @@ -46,7 +48,7 @@ class SearchActivity : ComponentActivity() { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> AppNavHost( viewModel = lyricsViewModel, - modifier = Modifier.padding(innerPadding) + modifier = Modifier.padding(innerPadding), ) } } @@ -60,4 +62,4 @@ class SearchActivity : ComponentActivity() { } } } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt index 8ba7e188..06f79de7 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt @@ -9,13 +9,19 @@ import com.tejpratapsingh.lyricsmaker.domain.ensureArrayList import com.tejpratapsingh.lyricsmaker.presentation.activity.LyricsActivity import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel -sealed class Screen(val route: String) { +sealed class Screen( + val route: String, +) { object Home : Screen("home") + object Lyrics : Screen("lyrics") } @Composable -fun AppNavHost(viewModel: LyricsViewModel, modifier: Modifier) { +fun AppNavHost( + viewModel: LyricsViewModel, + modifier: Modifier, +) { val navController = rememberNavController() NavHost(navController = navController, startDestination = Screen.Home.route) { @@ -26,7 +32,7 @@ fun AppNavHost(viewModel: LyricsViewModel, modifier: Modifier) { onLyricsSelected = { viewModel.selectedLyricResponse = it navController.navigate(Screen.Lyrics.route) - } + }, ) } @@ -42,9 +48,9 @@ fun AppNavHost(viewModel: LyricsViewModel, modifier: Modifier) { context = navController.context, song = viewModel.selectedSongName, lyrics = viewModel.selectedLyrics.ensureArrayList(), - socialMeta = viewModel.socialMeta.value + socialMeta = viewModel.socialMeta.value, ) - } + }, ) } } diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SearchLyricsCompose.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SearchLyricsCompose.kt index eab5ab29..c137bb4d 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SearchLyricsCompose.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SearchLyricsCompose.kt @@ -38,7 +38,7 @@ import kotlinx.coroutines.launch fun SearchScreen( modifier: Modifier = Modifier, viewModel: LyricsViewModel, - onLyricsSelected: (LyricsResponse) -> Unit = {} + onLyricsSelected: (LyricsResponse) -> Unit = {}, ) { val context = LocalContext.current val query = viewModel.query.collectAsState() @@ -56,16 +56,18 @@ fun SearchScreen( val keyboardController = LocalSoftwareKeyboardController.current Column( - modifier = modifier - .fillMaxSize() - .padding(16.dp) + modifier = + modifier + .fillMaxSize() + .padding(16.dp), ) { Text( text = "Search Lyrics", style = MaterialTheme.typography.headlineLarge, - modifier = Modifier - .align(CenterHorizontally) - .padding(16.dp) + modifier = + Modifier + .align(CenterHorizontally) + .padding(16.dp), ) OutlinedTextField( value = query.value, @@ -76,25 +78,26 @@ fun SearchScreen( if (isLoading.value) { CircularProgressIndicator( modifier = Modifier.size(20.dp), - strokeWidth = 2.dp + strokeWidth = 2.dp, ) } }, keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Search), - keyboardActions = KeyboardActions( - onSearch = { - coroutineScope.launch { - val searchQuery = query.value.trim() - if (searchQuery.isNotBlank()) { - keyboardController?.hide() - RecentSearchHelper.saveSearch(context, searchQuery) - recentSearches.value = RecentSearchHelper.getSearches(context) - viewModel.fetchLyrics() + keyboardActions = + KeyboardActions( + onSearch = { + coroutineScope.launch { + val searchQuery = query.value.trim() + if (searchQuery.isNotBlank()) { + keyboardController?.hide() + RecentSearchHelper.saveSearch(context, searchQuery) + recentSearches.value = RecentSearchHelper.getSearches(context) + viewModel.fetchLyrics() + } } - } - } - ), - modifier = Modifier.fillMaxWidth() + }, + ), + modifier = Modifier.fillMaxWidth(), ) if (lyrics.isEmpty()) { @@ -103,74 +106,82 @@ fun SearchScreen( LazyColumn { items(recentSearches.value.size) { idx -> Card( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) - .clickable { - viewModel.query.tryEmit(recentSearches.value[idx]) - coroutineScope.launch { - keyboardController?.hide() - viewModel.fetchLyrics() - } - }, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant - ) + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clickable { + viewModel.query.tryEmit(recentSearches.value[idx]) + coroutineScope.launch { + keyboardController?.hide() + viewModel.fetchLyrics() + } + }, + colors = + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), ) { Text( text = recentSearches.value[idx], - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) + modifier = + Modifier + .fillMaxWidth() + .padding(16.dp), ) } } } } else { LazyColumn( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { items(lyrics.size) { item -> Card( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) - .clickable { onLyricsSelected(lyrics[item]) }, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant - ) + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clickable { onLyricsSelected(lyrics[item]) }, + colors = + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), ) { Text( text = "${lyrics[item].trackName} - ${lyrics[item].artistName}", style = MaterialTheme.typography.labelLarge, - modifier = Modifier.padding( - start = 16.dp, - top = 16.dp, - end = 16.dp, - bottom = 2.dp - ) + modifier = + Modifier.padding( + start = 16.dp, + top = 16.dp, + end = 16.dp, + bottom = 2.dp, + ), ) Text( text = "Duration: ${lyrics[item].getReadableDuration()}", maxLines = 2, style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding( - start = 16.dp, - top = 2.dp, - end = 16.dp, - bottom = 2.dp - ) + modifier = + Modifier.padding( + start = 16.dp, + top = 2.dp, + end = 16.dp, + bottom = 2.dp, + ), ) Text( text = lyrics[item].getLyrics(), maxLines = 2, style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding( - start = 16.dp, - top = 2.dp, - end = 16.dp, - bottom = 16.dp - ) + modifier = + Modifier.padding( + start = 16.dp, + top = 2.dp, + end = 16.dp, + bottom = 16.dp, + ), ) Spacer(Modifier.height(4.dp)) } @@ -178,4 +189,4 @@ fun SearchScreen( } } } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt index 6df683e9..52962a76 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt @@ -38,9 +38,13 @@ import com.tejpratapsingh.motionlib.core.MotionConfig import kotlin.math.max import kotlin.math.min -data class RangeSelection(val start: Int, val end: Int) { +data class RangeSelection( + val start: Int, + val end: Int, +) { val minIndex get() = min(start, end) val maxIndex get() = max(start, end) + fun contains(index: Int) = index in minIndex..maxIndex } @@ -49,29 +53,29 @@ fun SyncedLyricsSelector( viewModel: LyricsViewModel, modifier: Modifier = Modifier, onSelectionChanged: (List) -> Unit = {}, - onFinalize: (List) -> Unit = {} + onFinalize: (List) -> Unit = {}, ) { val listState = rememberLazyListState() val haptics = LocalHapticFeedback.current var selection by remember { mutableStateOf(null) } Column(modifier = modifier.fillMaxSize()) { - // Selection summary bar if (selection != null) { val selected = viewModel.lyrics.subList(selection!!.minIndex, selection!!.maxIndex + 1) Surface(tonalElevation = 2.dp) { Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(8.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween + horizontalArrangement = Arrangement.SpaceBetween, ) { Text( "Selected ${selected.size} line(s)", style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.SemiBold + fontWeight = FontWeight.SemiBold, ) Row { TextButton(onClick = { onFinalize(selected) }) { @@ -95,39 +99,41 @@ fun SyncedLyricsSelector( LazyColumn( state = listState, modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(vertical = 8.dp) + contentPadding = PaddingValues(vertical = 8.dp), ) { itemsIndexed(viewModel.lyrics) { index, line -> val isSelected = selection?.contains(index) == true Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 4.dp) - .clip(MaterialTheme.shapes.medium) - .background( - if (isSelected) MaterialTheme.colorScheme.primary.copy(alpha = 0.15f) - else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f) - ) - .combinedClickable( - onClick = { - if (selection != null) { - selection = selection!!.copy(end = index) - } - }, - onLongClick = { - haptics.performHapticFeedback(HapticFeedbackType.LongPress) - selection = RangeSelection(index, index) - } - ) - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 4.dp) + .clip(MaterialTheme.shapes.medium) + .background( + if (isSelected) { + MaterialTheme.colorScheme.primary.copy(alpha = 0.15f) + } else { + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f) + }, + ).combinedClickable( + onClick = { + if (selection != null) { + selection = selection!!.copy(end = index) + } + }, + onLongClick = { + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + selection = RangeSelection(index, index) + }, + ).padding(12.dp), + verticalAlignment = Alignment.CenterVertically, ) { // Frame number Text( "[${line.frame}]", style = MaterialTheme.typography.labelMedium, - modifier = Modifier.width(64.dp) + modifier = Modifier.width(64.dp), ) Spacer(Modifier.width(8.dp)) // Lyric text @@ -135,18 +141,18 @@ fun SyncedLyricsSelector( text = line.text.ifEmpty { "…" }, style = MaterialTheme.typography.bodyLarge, fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) Spacer(Modifier.width(8.dp)) // Time in seconds Text( "[${line.frame / MotionConfig.fps} sec]", style = MaterialTheme.typography.labelMedium, - modifier = Modifier.width(64.dp) + modifier = Modifier.width(64.dp), ) } } } } } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/LyricsVideoProducer.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/LyricsVideoProducer.kt index 97100e45..d84a8654 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/LyricsVideoProducer.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/LyricsVideoProducer.kt @@ -14,27 +14,30 @@ fun getLyricsVideoProducer( applicationContext: Context, song: String, lyrics: List, - image: String? = null + image: String? = null, ): MotionVideoProducer { - Log.d("MotionVideoProducer", "getLyricsVideoProducer: ${lyrics.size}") - val motionConfig = MotionConfig( - aspectRatio = VideoAspectRatio.Ratio9x16_480, fps = 24 - ) + val motionConfig = + MotionConfig( + aspectRatio = VideoAspectRatio.Ratio9x16_480, + fps = 24, + ) - val motionView = LyricsContainer( - context = applicationContext, - startFrame = lyrics.first().frame, - endFrame = lyrics.last().frame, - songName = song, - lyrics = lyrics, - image = image - ) + val motionView = + LyricsContainer( + context = applicationContext, + startFrame = lyrics.first().frame, + endFrame = lyrics.last().frame, + songName = song, + lyrics = lyrics, + image = image, + ) - return MotionVideoProducer.with( - context = applicationContext, - config = motionConfig, - videoProducerAdapter = FfmpegVideoProducerAdapter() - ).addMotionViewToSequence(motionView = motionView) -} \ No newline at end of file + return MotionVideoProducer + .with( + context = applicationContext, + config = motionConfig, + videoProducerAdapter = FfmpegVideoProducerAdapter(), + ).addMotionViewToSequence(motionView = motionView) +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/MultiLyricsVideoProducer.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/MultiLyricsVideoProducer.kt index af59da29..13345f50 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/MultiLyricsVideoProducer.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/motion/MultiLyricsVideoProducer.kt @@ -11,20 +11,24 @@ import com.tejpratapsingh.motionlib.ffmpeg.FfmpegVideoProducerAdapter import com.tejpratapsingh.motionlib.ui.custom.text.WordBlinkTextView fun getMultiLyricsVideoProducer( - applicationContext: Context, song: String, lyrics: List + applicationContext: Context, + song: String, + lyrics: List, ): MotionVideoProducer { - Log.d("MotionVideoProducer", "getMultiLyricsVideoProducer: ${lyrics.size}") - val motionConfig = MotionConfig( - aspectRatio = VideoAspectRatio.Ratio9x16_480, fps = 24 - ) + val motionConfig = + MotionConfig( + aspectRatio = VideoAspectRatio.Ratio9x16_480, + fps = 24, + ) - val producer = MotionVideoProducer.with( - context = applicationContext, - config = motionConfig, - videoProducerAdapter = FfmpegVideoProducerAdapter() - ) + val producer = + MotionVideoProducer.with( + context = applicationContext, + config = motionConfig, + videoProducerAdapter = FfmpegVideoProducerAdapter(), + ) lyrics.zipWithNext().forEach { (current, next) -> producer.addMotionViewToSequence( @@ -33,15 +37,16 @@ fun getMultiLyricsVideoProducer( text = current.text, startFrame = current.frame, endFrame = next.frame, - textView = AppCompatTextView(applicationContext).apply { - textSize = 24f - setTextColor(android.graphics.Color.WHITE) - setPadding(16, 16, 16, 16) - textAlignment = AppCompatTextView.TEXT_ALIGNMENT_CENTER - } - ) + textView = + AppCompatTextView(applicationContext).apply { + textSize = 24f + setTextColor(android.graphics.Color.WHITE) + setPadding(16, 16, 16, 16) + textAlignment = AppCompatTextView.TEXT_ALIGNMENT_CENTER + }, + ), ) } return producer -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationChannelType.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationChannelType.kt index 678a30e3..ab683e46 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationChannelType.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationChannelType.kt @@ -9,18 +9,18 @@ enum class NotificationChannelType( val channelId: String, val channelNameResId: Int, // Changed to Int for resource ID val channelDescriptionResId: Int, // Changed to Int for resource ID - val importance: Int + val importance: Int, ) { RENDERING_PROGRESS( "render_progress_channel", R.string.notification_channel_rendering_progress_name, R.string.notification_channel_rendering_progress_description, - NotificationManager.IMPORTANCE_LOW + NotificationManager.IMPORTANCE_LOW, ), RENDERING_COMPLETED( "render_completed_channel", R.string.notification_channel_rendering_completed_name, R.string.notification_channel_rendering_completed_description, - NotificationManager.IMPORTANCE_DEFAULT - ); + NotificationManager.IMPORTANCE_DEFAULT, + ), } diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationFactory.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationFactory.kt index cd077f86..185d26a8 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationFactory.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/notification/NotificationFactory.kt @@ -19,30 +19,30 @@ class NotificationFactory { NotificationChannelType.entries.forEach { channelType -> // Check if channel already exists to avoid re-creating unnecessarily if (notificationManager.getNotificationChannel(channelType.channelId) == null) { - val channel = NotificationChannel( - channelType.channelId, - context.getString(channelType.channelNameResId), // Use string resource - channelType.importance - ).apply { - description = - context.getString(channelType.channelDescriptionResId) // Use string resource - } + val channel = + NotificationChannel( + channelType.channelId, + context.getString(channelType.channelNameResId), // Use string resource + channelType.importance, + ).apply { + description = + context.getString(channelType.channelDescriptionResId) // Use string resource + } notificationManager.createNotificationChannel(channel) } } } } - fun getRenderProgressNotification( - context: Context - ): NotificationCompat.Builder { + fun getRenderProgressNotification(context: Context): NotificationCompat.Builder { val channelType = NotificationChannelType.RENDERING_PROGRESS // Ensure channel is created. // If you move channel creation to Application class, this explicit call might not be needed here. createNotificationChannels(context) // Or a more optimized way to ensure channels are created - return NotificationCompat.Builder(context, channelType.channelId) + return NotificationCompat + .Builder(context, channelType.channelId) .setSmallIcon(R.drawable.ic_notification_burst) .setContentTitle(context.getString(R.string.notification_render_progress_title)) // Use string resource .setContentText(context.getString(R.string.notification_render_progress_text_starting)) // Use string resource @@ -51,15 +51,14 @@ class NotificationFactory { .setPriority(NotificationCompat.PRIORITY_LOW) } - fun getRenderCompleteNotification( - context: Context - ): NotificationCompat.Builder { + fun getRenderCompleteNotification(context: Context): NotificationCompat.Builder { val channelType = NotificationChannelType.RENDERING_COMPLETED // Ensure channel is created createNotificationChannels(context) // Or a more optimized way to ensure channels are created - return NotificationCompat.Builder(context, channelType.channelId) + return NotificationCompat + .Builder(context, channelType.channelId) .setSmallIcon(R.drawable.ic_notification_burst) .setContentTitle(context.getString(R.string.notification_render_complete_title)) // Use string resource .setContentText(context.getString(R.string.notification_render_complete_text)) // Use string resource diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Color.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Color.kt index b67d36bb..80d03734 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Color.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Color.kt @@ -8,4 +8,4 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt index 493dc638..6aa53c61 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt @@ -10,17 +10,18 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 +private val DarkColorScheme = + darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80, + ) +private val LightColorScheme = + lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40, /* Other default colors to override background = Color(0xFFFFFBFE), surface = Color(0xFFFFFBFE), @@ -29,29 +30,35 @@ private val LightColorScheme = lightColorScheme( onTertiary = Color.White, onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), - */ -) + */ + ) @Composable fun AnimatorTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } + val colorScheme = + when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> { + DarkColorScheme + } - darkTheme -> DarkColorScheme - else -> LightColorScheme - } + else -> { + LightColorScheme + } + } MaterialTheme( colorScheme = colorScheme, typography = Typography, - content = content + content = content, ) -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Type.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Type.kt index c3d5c14e..1eeaec0b 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Type.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Type.kt @@ -7,14 +7,16 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp // Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) +val Typography = + Typography( + bodyLarge = + TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), /* Other default text styles to override titleLarge = TextStyle( fontFamily = FontFamily.Default, @@ -30,5 +32,5 @@ val Typography = Typography( lineHeight = 16.sp, letterSpacing = 0.5.sp ) - */ -) \ No newline at end of file + */ + ) diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeAudioChartView.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeAudioChartView.kt index f8ce089f..d684f364 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeAudioChartView.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeAudioChartView.kt @@ -9,76 +9,85 @@ import android.view.View import kotlin.math.sin import kotlin.random.Random -class FakeAudioChartView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0 -) : View(context, attrs, defStyle) { - - private val barPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.WHITE - style = Paint.Style.FILL - } - private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.WHITE - alpha = 50 - strokeWidth = 2f - } - - var bars: Int = 48 - var barWidthPx: Float = 20f - var barSpacingPx: Float = 10f - var seed: Long = 12345L - - /** Control how fast bars move (smaller = slower, larger = faster) */ - var speedFactor: Float = 0.05f // try 0.01f for very slow, 0.1f for fast - - private val rngs by lazy { - List(bars) { Random(seed + it * 7919L) } - } - - /** Current frame externally controlled */ - private var frame: Int = 0 - - fun setFrame(frameNumber: Int) { - frame = frameNumber - invalidate() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val w = width.toFloat() - val h = height.toFloat() - val totalWidth = bars * barWidthPx + (bars - 1) * barSpacingPx - val startX = (w - totalWidth) / 2f - - for (i in 0 until bars) { - val r = rngs[i] - - // Make each bar evolve slower using speedFactor - val phase = (frame * speedFactor + i * 0.3f) % 1f - - // Use seeded noise with smooth oscillation instead of raw random - val base = 0.5f + 0.5f * sin((frame * speedFactor + i) * 2.0) - val noise = r.nextFloat() * 0.2f - val finalAmp = ((base + noise) * 0.9f).coerceIn(0.0, 1.0) +class FakeAudioChartView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, + ) : View(context, attrs, defStyle) { + private val barPaint = + Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + style = Paint.Style.FILL + } + private val linePaint = + Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + alpha = 50 + strokeWidth = 2f + } + + var bars: Int = 48 + var barWidthPx: Float = 20f + var barSpacingPx: Float = 10f + var seed: Long = 12345L + + /** Control how fast bars move (smaller = slower, larger = faster) */ + var speedFactor: Float = 0.05f // try 0.01f for very slow, 0.1f for fast + + private val rngs by lazy { + List(bars) { Random(seed + it * 7919L) } + } - val barHeight = (finalAmp * h).coerceAtLeast(4.0) - val x = startX + i * (barWidthPx + barSpacingPx) - val yTop: Float = (h - barHeight).toFloat() / 2f - val yBottom: Float = yTop + barHeight.toFloat() + /** Current frame externally controlled */ + private var frame: Int = 0 - canvas.drawRoundRect( - x, yTop, x + barWidthPx, yBottom, - barWidthPx / 2f, barWidthPx / 2f, barPaint - ) - canvas.drawLine( - x + barWidthPx / 2f, yTop, - x + barWidthPx / 2f, yBottom, - linePaint - ) + fun setFrame(frameNumber: Int) { + frame = frameNumber + invalidate() } + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val w = width.toFloat() + val h = height.toFloat() + val totalWidth = bars * barWidthPx + (bars - 1) * barSpacingPx + val startX = (w - totalWidth) / 2f + + for (i in 0 until bars) { + val r = rngs[i] + + // Make each bar evolve slower using speedFactor + val phase = (frame * speedFactor + i * 0.3f) % 1f + + // Use seeded noise with smooth oscillation instead of raw random + val base = 0.5f + 0.5f * sin((frame * speedFactor + i) * 2.0) + val noise = r.nextFloat() * 0.2f + val finalAmp = ((base + noise) * 0.9f).coerceIn(0.0, 1.0) + + val barHeight = (finalAmp * h).coerceAtLeast(4.0) + val x = startX + i * (barWidthPx + barSpacingPx) + val yTop: Float = (h - barHeight).toFloat() / 2f + val yBottom: Float = yTop + barHeight.toFloat() + + canvas.drawRoundRect( + x, + yTop, + x + barWidthPx, + yBottom, + barWidthPx / 2f, + barWidthPx / 2f, + barPaint, + ) + canvas.drawLine( + x + barWidthPx / 2f, + yTop, + x + barWidthPx / 2f, + yBottom, + linePaint, + ) + } + } } -} \ No newline at end of file diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeSineWaveView.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeSineWaveView.kt index d8b2db2c..15bd7c06 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeSineWaveView.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/FakeSineWaveView.kt @@ -8,66 +8,69 @@ import android.util.AttributeSet import android.view.View import kotlin.math.sin -class FakeSineWaveView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0 -) : View(context, attrs, defStyle) { +class FakeSineWaveView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, + ) : View(context, attrs, defStyle) { + private val wavePaint = + Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.MAGENTA + strokeWidth = 6f + style = Paint.Style.STROKE + } + private val textPaint = + Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + textSize = 32f + } - private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.MAGENTA - strokeWidth = 6f - style = Paint.Style.STROKE - } - private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.WHITE - textSize = 32f - } + /** externally controlled frame */ + private var frame: Int = 0 - /** externally controlled frame */ - private var frame: Int = 0 + /** control wave properties */ + var amplitude: Float = 0.3f // relative height of wave + var wavelength: Float = 150f // pixels per wave cycle + var speedFactor: Float = 0.05f // animation speed - /** control wave properties */ - var amplitude: Float = 0.3f // relative height of wave - var wavelength: Float = 150f // pixels per wave cycle - var speedFactor: Float = 0.05f // animation speed + fun setFrame(frameNumber: Int) { + frame = frameNumber + invalidate() + } - fun setFrame(frameNumber: Int) { - frame = frameNumber - invalidate() - } + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) + val w = width.toFloat() + val h = height.toFloat() + val centerY = h / 2f - val w = width.toFloat() - val h = height.toFloat() - val centerY = h / 2f + val pathPoints = mutableListOf() - val pathPoints = mutableListOf() + val phaseShift = frame * speedFactor - val phaseShift = frame * speedFactor + // Generate sine wave points + var x = 0f + while (x <= w) { + val angle = (x / wavelength * 2 * Math.PI + phaseShift).toFloat() + val y = centerY + sin(angle) * (h * amplitude) + pathPoints.add(x) + pathPoints.add(y) + x += 4f // step size (smaller = smoother curve) + } - // Generate sine wave points - var x = 0f - while (x <= w) { - val angle = (x / wavelength * 2 * Math.PI + phaseShift).toFloat() - val y = centerY + sin(angle) * (h * amplitude) - pathPoints.add(x) - pathPoints.add(y) - x += 4f // step size (smaller = smoother curve) - } + // Draw line segments between points + for (i in 2 until pathPoints.size step 2) { + val x1 = pathPoints[i - 2] + val y1 = pathPoints[i - 1] + val x2 = pathPoints[i] + val y2 = pathPoints[i + 1] + canvas.drawLine(x1, y1, x2, y2, wavePaint) + } - // Draw line segments between points - for (i in 2 until pathPoints.size step 2) { - val x1 = pathPoints[i - 2] - val y1 = pathPoints[i - 1] - val x2 = pathPoints[i] - val y2 = pathPoints[i + 1] - canvas.drawLine(x1, y1, x2, y2, wavePaint) + // Debug overlay + canvas.drawText("Frame: $frame", 20f, 40f, textPaint) } - - // Debug overlay - canvas.drawText("Frame: $frame", 20f, 40f, textPaint) } -} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt index 670acdcb..fd0cbfc9 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt @@ -31,9 +31,8 @@ class LyricsContainer( startFrame: Int, endFrame: Int, val lyrics: List, - image: String? = null + image: String? = null, ) : BaseFrameMotionView(context) { - companion object { private const val TAG = "LyricsContainer" } @@ -81,11 +80,11 @@ class LyricsContainer( return@runBlocking } else { Log.i(TAG, "Fetching from musicbrainz") - AlbumArtFetcher.fetchAlbumArtUrl( - songName.split(" - ")[0], - songName.split(" - ")[1] - ) - ?.let { url -> + AlbumArtFetcher + .fetchAlbumArtUrl( + songName.split(" - ")[0], + songName.split(" - ")[1], + )?.let { url -> Log.i(TAG, "cover art found: $url") setImageBitmap(AlbumArtFetcher.fetchAlbumArtBitmap(url)) AlbumArtFetcher.close() @@ -98,22 +97,24 @@ class LyricsContainer( override fun forFrame(frame: Int): MotionView { super.forFrame(frame) - val backgroundColor: Int = MotionInterpolator.interpolateColorForRange( - Interpolators(Easings.LINEAR), - frame, - Pair(startFrame, endFrame), - Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()) - ) + val backgroundColor: Int = + MotionInterpolator.interpolateColorForRange( + Interpolators(Easings.LINEAR), + frame, + Pair(startFrame, endFrame), + Pair("#2568ff".toColorInt(), "#ba28ff".toColorInt()), + ) setBackgroundColor(Color.BLACK) - MotionInterpolator.getComplementaryColor( - backgroundColor - ).also { - tvSongName.setTextColor(it) - tvLyricsLine1.setTextColor(it) - tvLyricsLine2.setTextColor(it) - } + MotionInterpolator + .getComplementaryColor( + backgroundColor, + ).also { + tvSongName.setTextColor(it) + tvLyricsLine1.setTextColor(it) + tvLyricsLine2.setTextColor(it) + } fakeChartView.setFrame(frame) @@ -128,4 +129,4 @@ class LyricsContainer( } override fun getViewBitmap(): Bitmap = this.toBitmap() -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsTextView.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsTextView.kt index 3b604d74..abba3d84 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsTextView.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsTextView.kt @@ -13,16 +13,15 @@ class LyricsTextView( startFrame: Int, endFrame: Int, textView: AppCompatTextView = AppCompatTextView(context), - fontUrl: String? = null + fontUrl: String? = null, ) : AbstractMotionTextView( - context = context, - text = lyrics.first().text, - startFrame = startFrame, - endFrame = endFrame, - textView = textView, - fontUrl = fontUrl -) { - + context = context, + text = lyrics.first().text, + startFrame = startFrame, + endFrame = endFrame, + textView = textView, + fontUrl = fontUrl, + ) { override fun forFrame(frame: Int): MotionView { super.forFrame(frame) @@ -31,4 +30,4 @@ class LyricsTextView( return this } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/SongNameTextView.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/SongNameTextView.kt index 5dd025a3..48a3ab4f 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/SongNameTextView.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/SongNameTextView.kt @@ -11,9 +11,8 @@ class SongNameTextView( startFrame: Int, endFrame: Int, textView: AppCompatTextView = AppCompatTextView(context), - fontUrl: String? = null + fontUrl: String? = null, ) : AbstractMotionTextView(context, songName, startFrame, endFrame, textView, fontUrl) { - init { textView.text = songName } @@ -22,4 +21,4 @@ class SongNameTextView( super.forFrame(frame) return this } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt index 0f25fe0f..0b834360 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow open class LyricsViewModel : ViewModel() { - val socialMeta = MutableStateFlow(null) val query = MutableStateFlow("") val isLoading = MutableStateFlow(false) @@ -23,36 +22,40 @@ open class LyricsViewModel : ViewModel() { suspend fun fetchLyrics() { isLoading.value = true - val results = client.searchLyrics(SearchQuery(query.value)).filter { - it.syncedLyrics != null - } + val results = + client.searchLyrics(SearchQuery(query.value)).filter { + it.syncedLyrics != null + } _lyricsList.emit(results) isLoading.value = false } - var selectedLyricResponse: LyricsResponse = LyricsResponse( - id = 0, - trackName = "", - artistName = "", - ) + var selectedLyricResponse: LyricsResponse = + LyricsResponse( + id = 0, + trackName = "", + artistName = "", + ) val selectedSongName: String get() = "${selectedLyricResponse.trackName} - ${selectedLyricResponse.artistName}" val lyrics: List - get() = LrcHelper.getSyncedLyrics( - lrcContent = selectedLyricResponse.getLyrics(), - fps = MotionConfig.fps, - ) + get() = + LrcHelper.getSyncedLyrics( + lrcContent = selectedLyricResponse.getLyrics(), + fps = MotionConfig.fps, + ) var selectedLyrics: List = emptyList() get() { val firstFrame = field.first().frame - return field.map { - SyncedLyricFrame( - frame = it.frame - firstFrame, - text = it.text - ) - }.sortedBy { it.frame } + return field + .map { + SyncedLyricFrame( + frame = it.frame - firstFrame, + text = it.text, + ) + }.sortedBy { it.frame } } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt index 094f90d3..d76dfefc 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt @@ -32,9 +32,10 @@ import java.net.URLConnection import java.util.Locale import java.util.UUID -class LyricsMotionWorker(private val appContext: Context, parameters: WorkerParameters) : - MotionWorker(appContext, parameters) { - +class LyricsMotionWorker( + private val appContext: Context, + parameters: WorkerParameters, +) : MotionWorker(appContext, parameters) { private val notificationManager = NotificationManagerCompat.from(appContext) private val progressNotificationBuilder: NotificationCompat.Builder by lazy { @@ -46,49 +47,61 @@ class LyricsMotionWorker(private val appContext: Context, parameters: WorkerPara } private fun createForegroundInfo( - progressNotificationId: Int, notification: Notification - ): ForegroundInfo { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + progressNotificationId: Int, + notification: Notification, + ): ForegroundInfo = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { ForegroundInfo( progressNotificationId, notification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING, ) } else { ForegroundInfo(progressNotificationId, notification) } - } override suspend fun getForegroundInfo(): ForegroundInfo { // Create the notification for the foreground service val notification = - progressNotificationBuilder.setContentTitle("Rendering Video...") // Initial title + progressNotificationBuilder + .setContentTitle("Rendering Video...") // Initial title .setProgress(0, 0, true) // Indeterminate progress initially - .setOngoing(true).build() + .setOngoing(true) + .build() return createForegroundInfo(progressNotificationId, notification) } - override fun getMotionVideo(inputData: Data): MotionVideoProducer { - return getLyricsVideoProducer( + override fun getMotionVideo(inputData: Data): MotionVideoProducer = + getLyricsVideoProducer( applicationContext = appContext, song = inputData.getString(SONG) ?: "Unknown Song", lyrics = Json.decodeFromString(inputData.getString(LYRICS)!!), image = inputData.getString(IMAGE), ) - } - override fun onProgress(totalFrames: Int, currentProgress: Int, bitmap: Bitmap) { + override fun onProgress( + totalFrames: Int, + currentProgress: Int, + bitmap: Bitmap, + ) { Log.d(TAG, "onProgress: $currentProgress / $totalFrames") val percentage = (currentProgress.toDouble() / totalFrames) * 100 - val progressText = String.format( - Locale.getDefault(), "%d/%d frames completed", currentProgress, totalFrames - ) + val progressText = + String.format( + Locale.getDefault(), + "%d/%d frames completed", + currentProgress, + totalFrames, + ) val contentText = String.format(Locale.getDefault(), "%.0f%%", percentage) val notification = - progressNotificationBuilder.setProgress(totalFrames, currentProgress, false) - .setSubText(progressText).setContentText(contentText).build() + progressNotificationBuilder + .setProgress(totalFrames, currentProgress, false) + .setSubText(progressText) + .setContentText(contentText) + .build() updateNotification(progressNotificationId, notification) @@ -107,19 +120,24 @@ class LyricsMotionWorker(private val appContext: Context, parameters: WorkerPara val intentOpenFile = Intent(Intent.ACTION_VIEW) val pendingOpenFileIntent = createPendingIntentFor(intentOpenFile, videoFile) - val completedNotification = completedNotificationBuilder.setContentTitle("Render Complete") - .setContentText("Video ready: ${videoFile.name}").addAction( - NotificationCompat.Action( - R.drawable.ic_menu_share, // Consider using a custom icon - "Share Video", pendingShareIntent - ) - ).addAction( - NotificationCompat.Action( - R.drawable.ic_media_play, // Consider using a custom icon - "Open Video", pendingOpenFileIntent - ) - ).setAutoCancel(true) // Dismiss notification when tapped (if no content intent set) - .build() + val completedNotification = + completedNotificationBuilder + .setContentTitle("Render Complete") + .setContentText("Video ready: ${videoFile.name}") + .addAction( + NotificationCompat.Action( + R.drawable.ic_menu_share, // Consider using a custom icon + "Share Video", + pendingShareIntent, + ), + ).addAction( + NotificationCompat.Action( + R.drawable.ic_media_play, // Consider using a custom icon + "Open Video", + pendingOpenFileIntent, + ), + ).setAutoCancel(true) // Dismiss notification when tapped (if no content intent set) + .build() updateNotification(completedNotificationId, completedNotification) } @@ -127,7 +145,10 @@ class LyricsMotionWorker(private val appContext: Context, parameters: WorkerPara @Volatile private var lastNotificationUpdateTime = 0L - private fun updateNotification(notificationId: Int, notification: Notification) { + private fun updateNotification( + notificationId: Int, + notification: Notification, + ) { val currentTime = System.currentTimeMillis() if (currentTime - lastNotificationUpdateTime < 500) { return @@ -135,7 +156,8 @@ class LyricsMotionWorker(private val appContext: Context, parameters: WorkerPara lastNotificationUpdateTime = currentTime if (ActivityCompat.checkSelfPermission( - appContext, Manifest.permission.POST_NOTIFICATIONS + appContext, + Manifest.permission.POST_NOTIFICATIONS, ) == PackageManager.PERMISSION_GRANTED ) { notificationManager.notify(notificationId, notification) @@ -146,12 +168,19 @@ class LyricsMotionWorker(private val appContext: Context, parameters: WorkerPara } } - private fun createPendingIntentFor(intent: Intent, videoFile: File): PendingIntent { - val apkURI: Uri = FileProvider.getUriForFile( - appContext, "${appContext.packageName}.fileprovider", videoFile - ) + private fun createPendingIntentFor( + intent: Intent, + videoFile: File, + ): PendingIntent { + val apkURI: Uri = + FileProvider.getUriForFile( + appContext, + "${appContext.packageName}.fileprovider", + videoFile, + ) intent.setDataAndType( - apkURI, URLConnection.guessContentTypeFromName(videoFile.name) + apkURI, + URLConnection.guessContentTypeFromName(videoFile.name), ) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.putExtra(Intent.EXTRA_STREAM, apkURI) @@ -160,8 +189,10 @@ class LyricsMotionWorker(private val appContext: Context, parameters: WorkerPara PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE return PendingIntent.getActivity( - appContext, 0, // requestCode, consider making this unique if you have many such intents - intent, pendingShareIntentFlags + appContext, + 0, // requestCode, consider making this unique if you have many such intents + intent, + pendingShareIntentFlags, ) } @@ -176,13 +207,15 @@ class LyricsMotionWorker(private val appContext: Context, parameters: WorkerPara context: Context, song: String, lyrics: List, - image: String? = null + image: String? = null, ): UUID { - val inputData = Data.Builder() - .putString(SONG, song) - .putString(LYRICS, Json.encodeToString(lyrics)) - .putString(IMAGE, image) - .build() + val inputData = + Data + .Builder() + .putString(SONG, song) + .putString(LYRICS, Json.encodeToString(lyrics)) + .putString(IMAGE, image) + .build() val workRequest = OneTimeWorkRequestBuilder().setInputData(inputData).build() diff --git a/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/ExampleUnitTest.kt b/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/ExampleUnitTest.kt index 38a1b570..64c113d6 100644 --- a/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/ExampleUnitTest.kt +++ b/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/metadata-extractor/src/androidTest/java/com/tejpratapsingh/motion/metadataextractor/ExampleInstrumentedTest.kt b/modules/metadata-extractor/src/androidTest/java/com/tejpratapsingh/motion/metadataextractor/ExampleInstrumentedTest.kt index d0f9dc5e..69a35881 100644 --- a/modules/metadata-extractor/src/androidTest/java/com/tejpratapsingh/motion/metadataextractor/ExampleInstrumentedTest.kt +++ b/modules/metadata-extractor/src/androidTest/java/com/tejpratapsingh/motion/metadataextractor/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.tejpratapsingh.motion.metadataextractor -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.tejpratapsingh.motion.metadataextractor.test", appContext.packageName) } -} \ No newline at end of file +} diff --git a/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/MetadataExtractor.kt b/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/MetadataExtractor.kt index 03fae00e..b98ebcb6 100644 --- a/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/MetadataExtractor.kt +++ b/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/MetadataExtractor.kt @@ -14,7 +14,7 @@ data class SocialMeta( val image: String? = null, val siteName: String? = null, val twitterCard: String? = null, - val url: String? = null + val url: String? = null, ) : Parcelable suspend fun HttpClient.extractSocialMetadata(url: String): SocialMeta? { @@ -36,7 +36,7 @@ suspend fun HttpClient.extractSocialMetadata(url: String): SocialMeta? { image = metaContent("og:image", "twitter:image"), siteName = metaContent("og:site_name"), twitterCard = metaContent("twitter:card"), - url = metaContent("og:url", "twitter:url") + url = metaContent("og:url", "twitter:url"), ) } catch (e: Exception) { e.printStackTrace() diff --git a/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/ShareReceiverActivity.kt b/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/ShareReceiverActivity.kt index 31ee618f..8228ce77 100644 --- a/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/ShareReceiverActivity.kt +++ b/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/ShareReceiverActivity.kt @@ -18,20 +18,18 @@ import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.launch class ShareReceiverActivity : AppCompatActivity() { - companion object { private const val TAG = "ShareReceiverActivity" const val EXTRA_METADATA = "extra_metadata" const val ACTIVITY_INTENT_ACTION = "com.tejpratapsingh.motion.metadataextractor.action.OPEN" - fun readMetadataFromIntent(intent: Intent): SocialMeta? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + fun readMetadataFromIntent(intent: Intent): SocialMeta? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(EXTRA_METADATA, SocialMeta::class.java) } else { intent.getParcelableExtra(EXTRA_METADATA) as SocialMeta? } - } } override fun onCreate(savedInstanceState: Bundle?) { @@ -47,8 +45,10 @@ class ShareReceiverActivity : AppCompatActivity() { when (intent?.action) { Intent.ACTION_SEND -> { when (intent.type) { - "text/plain" -> lifecycleScope.launch { - handleSharedText(intent) + "text/plain" -> { + lifecycleScope.launch { + handleSharedText(intent) + } } else -> { @@ -79,13 +79,15 @@ class ShareReceiverActivity : AppCompatActivity() { findViewById(R.id.btn_next).also { btn -> btn.setOnClickListener { - startActivity(Intent(ACTIVITY_INTENT_ACTION).apply { - putExtra(EXTRA_METADATA, metaData) - }) + startActivity( + Intent(ACTIVITY_INTENT_ACTION).apply { + putExtra(EXTRA_METADATA, metaData) + }, + ) finish() } } } } } -} \ No newline at end of file +} diff --git a/modules/metadata-extractor/src/test/java/com/tejpratapsingh/motion/metadataextractor/ExampleUnitTest.kt b/modules/metadata-extractor/src/test/java/com/tejpratapsingh/motion/metadataextractor/ExampleUnitTest.kt index 498355a7..9b27e140 100644 --- a/modules/metadata-extractor/src/test/java/com/tejpratapsingh/motion/metadataextractor/ExampleUnitTest.kt +++ b/modules/metadata-extractor/src/test/java/com/tejpratapsingh/motion/metadataextractor/ExampleUnitTest.kt @@ -1,8 +1,7 @@ package com.tejpratapsingh.motion.metadataextractor -import org.junit.Test - import org.junit.Assert.* +import org.junit.Test /** * Example local unit test, which will execute on the development machine (host). @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/activities/PreviewActivity.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/activities/PreviewActivity.kt index e7a17661..a040e17e 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/activities/PreviewActivity.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/activities/PreviewActivity.kt @@ -10,7 +10,6 @@ import com.tejpratapsingh.motionlib.core.motion.MotionVideoProducer import com.tejpratapsingh.motionlib.ui.MotionVideoPlayer abstract class PreviewActivity : ComponentActivity() { - val motionVideoPlayer by lazy { MotionVideoPlayer(applicationContext, getMotionVideo()) } override fun onCreate(savedInstanceState: Bundle?) { @@ -22,7 +21,10 @@ abstract class PreviewActivity : ComponentActivity() { ViewCompat.setOnApplyWindowInsetsListener(motionVideoPlayer) { view, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) view.updatePadding( - left = insets.left, top = insets.top, right = insets.right, bottom = insets.bottom + left = insets.left, + top = insets.top, + right = insets.right, + bottom = insets.bottom, ) WindowInsetsCompat.CONSUMED } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/adapter/AndroidVideoProducerAdapter.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/adapter/AndroidVideoProducerAdapter.kt index 2d6c52a3..e737bd09 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/adapter/AndroidVideoProducerAdapter.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/adapter/AndroidVideoProducerAdapter.kt @@ -14,7 +14,6 @@ import java.io.File import java.util.Locale class AndroidVideoProducerAdapter : VideoProducerAdapter { - companion object { private const val TAG = "AndroidVideoProducerAda" } @@ -30,7 +29,7 @@ class AndroidVideoProducerAdapter : VideoProducerAdapter { motionAudio: List, totalFrames: Int, outputFile: File, - progressListener: ((Int, Bitmap) -> Unit)? + progressListener: ((Int, Bitmap) -> Unit)?, ): File { Log.i(TAG, "produceVideo: starting") if (outputFile.exists()) { @@ -46,12 +45,16 @@ class AndroidVideoProducerAdapter : VideoProducerAdapter { for (i in 1..totalFrames) { Log.d(TAG, "produceVideo: frame $i") val frameBitmap: Bitmap = - motionComposerView.forFrame(i).getViewBitmap() + motionComposerView + .forFrame(i) + .getViewBitmap() .compressToBitmap(motionConfig.outputQuality) try { context.saveBitmapToCacheFolder( - frameBitmap, subDirName, String.format(Locale.getDefault(), "%05d.png", i) + frameBitmap, + subDirName, + String.format(Locale.getDefault(), "%05d.png", i), ) } catch (e: Exception) { Log.e(TAG, "Error saving frame $i: ${e.message}", e) @@ -68,9 +71,9 @@ class AndroidVideoProducerAdapter : VideoProducerAdapter { inputDir = subDir, motionAudio = motionAudio, outputFile = outputFile, - motionConfig = motionConfig + motionConfig = motionConfig, ) return outputFile } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Easings.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Easings.kt index 06979b9f..1181e582 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Easings.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Easings.kt @@ -32,4 +32,4 @@ enum class Easings { BOUNCE_IN, BOUNCE_OUT, BOUNCE_IN_OUT, -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Interpolators.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Interpolators.kt index d93dc06d..bd0481e7 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Interpolators.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Interpolators.kt @@ -7,39 +7,41 @@ import kotlin.math.asin import kotlin.math.pow import kotlin.math.sin -class Interpolators(val easing: Easings) : Interpolator { - - private var interpolator: Interpolator = when (easing) { - Easings.LINEAR -> PathInterpolatorCompat.create(0f, 0f, 1f, 1f) - Easings.SIN_IN -> PathInterpolatorCompat.create(.47f, 0f, .745f, .715f) - Easings.SIN_OUT -> PathInterpolatorCompat.create(.39f, .575f, .565f, 1f) - Easings.SIN_IN_OUT -> PathInterpolatorCompat.create(.445f, .05f, .55f, .95f) - Easings.QUAD_IN -> PathInterpolatorCompat.create(.55f, .085f, .68f, .53f) - Easings.QUAD_OUT -> PathInterpolatorCompat.create(.25f, .46f, .45f, .94f) - Easings.QUAD_IN_OUT -> PathInterpolatorCompat.create(.455f, .03f, .515f, .955f) - Easings.CUBIC_IN -> PathInterpolatorCompat.create(.55f, .055f, .675f, .19f) - Easings.CUBIC_OUT -> PathInterpolatorCompat.create(.215f, .61f, .355f, 1f) - Easings.CUBIC_IN_OUT -> PathInterpolatorCompat.create(.645f, .045f, .355f, 1f) - Easings.QUART_IN -> PathInterpolatorCompat.create(.895f, .03f, .685f, .22f) - Easings.QUART_OUT -> PathInterpolatorCompat.create(.165f, .84f, .44f, 1f) - Easings.QUART_IN_OUT -> PathInterpolatorCompat.create(.77f, 0f, .175f, 1f) - Easings.QUINT_IN -> PathInterpolatorCompat.create(.755f, .05f, .855f, .06f) - Easings.QUINT_OUT -> PathInterpolatorCompat.create(.23f, 1f, .32f, 1f) - Easings.QUINT_IN_OUT -> PathInterpolatorCompat.create(.86f, 0f, .07f, 1f) - Easings.EXP_IN -> PathInterpolatorCompat.create(.95f, .05f, .795f, .035f) - Easings.EXP_OUT -> PathInterpolatorCompat.create(.19f, 1f, .22f, 1f) - Easings.EXP_IN_OUT -> PathInterpolatorCompat.create(1f, 0f, 0f, 1f) - Easings.CIRC_IN -> PathInterpolatorCompat.create(.6f, .04f, .98f, .335f) - Easings.CIRC_OUT -> PathInterpolatorCompat.create(.075f, .82f, .165f, 1f) - Easings.CIRC_IN_OUT -> PathInterpolatorCompat.create(.785f, .135f, .15f, .86f) - Easings.BACK_IN -> PathInterpolatorCompat.create(.6f, -.28f, .735f, .045f) - Easings.BACK_OUT -> PathInterpolatorCompat.create(.175f, .885f, .32f, 1.275f) - Easings.BACK_IN_OUT -> PathInterpolatorCompat.create(.68f, -.55f, .265f, 1.55f) - else -> LinearInterpolator() - } +class Interpolators( + val easing: Easings, +) : Interpolator { + private var interpolator: Interpolator = + when (easing) { + Easings.LINEAR -> PathInterpolatorCompat.create(0f, 0f, 1f, 1f) + Easings.SIN_IN -> PathInterpolatorCompat.create(.47f, 0f, .745f, .715f) + Easings.SIN_OUT -> PathInterpolatorCompat.create(.39f, .575f, .565f, 1f) + Easings.SIN_IN_OUT -> PathInterpolatorCompat.create(.445f, .05f, .55f, .95f) + Easings.QUAD_IN -> PathInterpolatorCompat.create(.55f, .085f, .68f, .53f) + Easings.QUAD_OUT -> PathInterpolatorCompat.create(.25f, .46f, .45f, .94f) + Easings.QUAD_IN_OUT -> PathInterpolatorCompat.create(.455f, .03f, .515f, .955f) + Easings.CUBIC_IN -> PathInterpolatorCompat.create(.55f, .055f, .675f, .19f) + Easings.CUBIC_OUT -> PathInterpolatorCompat.create(.215f, .61f, .355f, 1f) + Easings.CUBIC_IN_OUT -> PathInterpolatorCompat.create(.645f, .045f, .355f, 1f) + Easings.QUART_IN -> PathInterpolatorCompat.create(.895f, .03f, .685f, .22f) + Easings.QUART_OUT -> PathInterpolatorCompat.create(.165f, .84f, .44f, 1f) + Easings.QUART_IN_OUT -> PathInterpolatorCompat.create(.77f, 0f, .175f, 1f) + Easings.QUINT_IN -> PathInterpolatorCompat.create(.755f, .05f, .855f, .06f) + Easings.QUINT_OUT -> PathInterpolatorCompat.create(.23f, 1f, .32f, 1f) + Easings.QUINT_IN_OUT -> PathInterpolatorCompat.create(.86f, 0f, .07f, 1f) + Easings.EXP_IN -> PathInterpolatorCompat.create(.95f, .05f, .795f, .035f) + Easings.EXP_OUT -> PathInterpolatorCompat.create(.19f, 1f, .22f, 1f) + Easings.EXP_IN_OUT -> PathInterpolatorCompat.create(1f, 0f, 0f, 1f) + Easings.CIRC_IN -> PathInterpolatorCompat.create(.6f, .04f, .98f, .335f) + Easings.CIRC_OUT -> PathInterpolatorCompat.create(.075f, .82f, .165f, 1f) + Easings.CIRC_IN_OUT -> PathInterpolatorCompat.create(.785f, .135f, .15f, .86f) + Easings.BACK_IN -> PathInterpolatorCompat.create(.6f, -.28f, .735f, .045f) + Easings.BACK_OUT -> PathInterpolatorCompat.create(.175f, .885f, .32f, 1.275f) + Easings.BACK_IN_OUT -> PathInterpolatorCompat.create(.68f, -.55f, .265f, 1.55f) + else -> LinearInterpolator() + } - override fun getInterpolation(value: Float): Float { - return when (easing) { + override fun getInterpolation(value: Float): Float = + when (easing) { Easings.ELASTIC_IN -> elasticIn(value) Easings.ELASTIC_OUT -> elasticOut(value) Easings.ELASTIC_IN_OUT -> elasticInOut(value) @@ -48,15 +50,15 @@ class Interpolators(val easing: Easings) : Interpolator { Easings.BOUNCE_IN_OUT -> bounceInOut(value) else -> interpolator.getInterpolation(value) } - } - private fun bounceIn(t: Float): Float { - return 1f - bounceOut(1f - t) - } + private fun bounceIn(t: Float): Float = 1f - bounceOut(1f - t) + + private fun bounceOut(t: Float): Float = + when { + t < 1 / 2.75 -> { + (7.5625 * t * t).toFloat() + } - private fun bounceOut(t: Float): Float { - return when { - t < 1 / 2.75 -> (7.5625 * t * t).toFloat() t < 2 / 2.75 -> { val o = t - 1.5 / 2.75 (7.5625 * o * o + 0.75).toFloat() @@ -72,7 +74,6 @@ class Interpolators(val easing: Easings) : Interpolator { (7.5625 * o * o + 0.984375).toFloat() } } - } private fun bounceInOut(t: Float): Float { if (t < 0.5) { @@ -121,4 +122,4 @@ class Interpolators(val easing: Easings) : Interpolator { (2.0.pow((-10 * o).toDouble()) * sin((o - s) * pi2 / .45) * 0.5 + 1).toFloat() } } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/MotionInterpolator.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/MotionInterpolator.kt index 0be4c61a..cba79aa2 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/MotionInterpolator.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/MotionInterpolator.kt @@ -30,7 +30,7 @@ object MotionInterpolator { interpolator: Interpolator, currentFrame: Int, frameRange: Pair, - valueRange: Pair + valueRange: Pair, ): Float { val (startFrame, endFrame) = frameRange val (startValue, endValue) = valueRange @@ -57,7 +57,7 @@ object MotionInterpolator { if (BuildConfig.DEBUG) { // Example: Only log in debug builds Log.d( TAG, - "interpolateForRange: currentFrame: $currentFrame, framePercent: $framePercent, interpolatedFramePercent: $interpolatedFramePercent, valueFromPercent: $valueFromPercent" + "interpolateForRange: currentFrame: $currentFrame, framePercent: $framePercent, interpolatedFramePercent: $interpolatedFramePercent, valueFromPercent: $valueFromPercent", ) } @@ -76,7 +76,7 @@ object MotionInterpolator { interpolator: Interpolator, currentFrame: Int, frameRange: Pair, - @ColorInt valueRange: Pair // Added @ColorInt for clarity + @ColorInt valueRange: Pair, // Added @ColorInt for clarity ): Int { val (startFrame, endFrame) = frameRange val (startColor, endColor) = valueRange @@ -97,7 +97,7 @@ object MotionInterpolator { return argbEvaluator.evaluate( interpolatedFramePercent, startColor, - endColor + endColor, ) as Int } @@ -106,11 +106,15 @@ object MotionInterpolator { * @param colorToInvert The @ColorInt to invert. * @return The complementary @ColorInt. */ - fun getComplementaryColor(@ColorInt colorToInvert: Int): Int { + fun getComplementaryColor( + @ColorInt colorToInvert: Int, + ): Int { val hsv = FloatArray(3) Color.RGBToHSV( - Color.red(colorToInvert), Color.green(colorToInvert), - Color.blue(colorToInvert), hsv + Color.red(colorToInvert), + Color.green(colorToInvert), + Color.blue(colorToInvert), + hsv, ) hsv[0] = (hsv[0] + 180) % 360 // Add 180 degrees to hue for complementary color return Color.HSVToColor(hsv) diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt index 1fb1bfb3..7719d621 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt @@ -10,15 +10,17 @@ import kotlin.math.sqrt object Spring { data class Config( val stiffness: Double = 100.0, // k - val damping: Double = 10.0, // c - val mass: Double = 1.0 // m + val damping: Double = 10.0, // c + val mass: Double = 1.0, // m ) - enum class Preset(val cfg: Config) { + enum class Preset( + val cfg: Config, + ) { GENTLE(Config(stiffness = 120.0, damping = 14.0, mass = 1.0)), WOBBLY(Config(stiffness = 180.0, damping = 12.0, mass = 1.0)), STIFF(Config(stiffness = 300.0, damping = 30.0, mass = 1.0)), - SLOW(Config(stiffness = 70.0, damping = 10.0, mass = 1.0)) + SLOW(Config(stiffness = 70.0, damping = 10.0, mass = 1.0)), } /** @@ -39,7 +41,7 @@ object Spring { from: Double = 0.0, to: Double = 1.0, config: Config = Config(), - initialVelocity: Double = 0.0 + initialVelocity: Double = 0.0, ): Double { if (from == to) return from val t = frame.coerceAtLeast(0) / fps @@ -57,51 +59,52 @@ object Spring { // normalized initial velocity (units of normalized value per second) val v0 = initialVelocity / delta - val xNormalized = when { - zeta < 1.0 -> { - // underdamped - val omegaD = omega0 * sqrt(1.0 - zeta * zeta) - // avoid division by zero if very small - if (omegaD.isFinite() && omegaD > 1e-12) { - val expTerm = exp(-zeta * omega0 * t) - val cosTerm = cos(omegaD * t) - val sinTerm = sin(omegaD * t) - 1.0 - (expTerm / omegaD) * ((v0 + zeta * omega0) * sinTerm + omegaD * cosTerm) - } else { - // fallback to simple exponential - 1.0 - exp(-omega0 * t) + val xNormalized = + when { + zeta < 1.0 -> { + // underdamped + val omegaD = omega0 * sqrt(1.0 - zeta * zeta) + // avoid division by zero if very small + if (omegaD.isFinite() && omegaD > 1e-12) { + val expTerm = exp(-zeta * omega0 * t) + val cosTerm = cos(omegaD * t) + val sinTerm = sin(omegaD * t) + 1.0 - (expTerm / omegaD) * ((v0 + zeta * omega0) * sinTerm + omegaD * cosTerm) + } else { + // fallback to simple exponential + 1.0 - exp(-omega0 * t) + } } - } - zeta == 1.0 -> { - // critically damped: double root at -omega0 - val expTerm = exp(-omega0 * t) - // formula for unit-step response with x(0)=0, x'(0)=v0: - 1.0 - expTerm * (1.0 + (v0 + omega0) * t) - } + zeta == 1.0 -> { + // critically damped: double root at -omega0 + val expTerm = exp(-omega0 * t) + // formula for unit-step response with x(0)=0, x'(0)=v0: + 1.0 - expTerm * (1.0 + (v0 + omega0) * t) + } - else -> { - // overdamped - val sqrtTerm = sqrt(zeta * zeta - 1.0) - val r1 = -omega0 * (zeta - sqrtTerm) - val r2 = -omega0 * (zeta + sqrtTerm) - // solve for coefficients A and B for homogeneous solution that satisfies initial conditions - // unit-step response => steady-state = 1 - // x(t) = 1 + A*exp(r1*t) + B*exp(r2*t) - // initial: - // x(0) = 0 => 1 + A + B = 0 => A + B = -1 - // x'(0) = v0 => A*r1 + B*r2 = v0 - val denom = (r1 - r2) - if (abs(denom) < 1e-12) { - // numerically degenerate; fallback - 1.0 - exp(-omega0 * t) - } else { - val A = (v0 - r2 * (-1.0)) / (r1 - r2) // solving linear system - val B = -1.0 - A - 1.0 + A * exp(r1 * t) + B * exp(r2 * t) + else -> { + // overdamped + val sqrtTerm = sqrt(zeta * zeta - 1.0) + val r1 = -omega0 * (zeta - sqrtTerm) + val r2 = -omega0 * (zeta + sqrtTerm) + // solve for coefficients A and B for homogeneous solution that satisfies initial conditions + // unit-step response => steady-state = 1 + // x(t) = 1 + A*exp(r1*t) + B*exp(r2*t) + // initial: + // x(0) = 0 => 1 + A + B = 0 => A + B = -1 + // x'(0) = v0 => A*r1 + B*r2 = v0 + val denom = (r1 - r2) + if (abs(denom) < 1e-12) { + // numerically degenerate; fallback + 1.0 - exp(-omega0 * t) + } else { + val A = (v0 - r2 * (-1.0)) / (r1 - r2) // solving linear system + val B = -1.0 - A + 1.0 + A * exp(r1 * t) + B * exp(r2 * t) + } } } - } // map normalized [0..1] back to [from..to] return from + delta * xNormalized @@ -121,7 +124,7 @@ object Spring { config: Config = Config(), initialVelocity: Double = 0.0, maxFrames: Int = 300, - settleThreshold: Double = 1e-3 + settleThreshold: Double = 1e-3, ): List { val out = ArrayList(min(maxFrames, 1000)) for (frame in 0 until maxFrames) { @@ -131,4 +134,4 @@ object Spring { } return out } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AndroidVideoGenerator.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AndroidVideoGenerator.kt index b9cafa89..bf25ebdd 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AndroidVideoGenerator.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AndroidVideoGenerator.kt @@ -17,7 +17,6 @@ import java.io.IOException import java.nio.ByteBuffer class AndroidVideoGenerator { - companion object { private const val TAG = "VideoGenerator" @@ -65,7 +64,7 @@ class AndroidVideoGenerator { inputDir: File? = null, outputFile: File, motionConfig: MotionConfig, - motionAudio: List = emptyList() + motionAudio: List = emptyList(), ) { if (bitmaps.isEmpty() && inputDir == null) { Log.w(TAG, "No bitmaps provided. Cannot generate video.") @@ -81,11 +80,12 @@ class AndroidVideoGenerator { val format = MediaFormat.createVideoFormat(MIME_TYPE, motionConfig.aspectRatio.width, motionConfig.aspectRatio.height) format.setInteger( - MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface + MediaFormat.KEY_COLOR_FORMAT, + MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, ) format.setInteger( MediaFormat.KEY_BIT_RATE, - calculateBitRate(motionConfig.aspectRatio.width, motionConfig.aspectRatio.height, motionConfig.fps) + calculateBitRate(motionConfig.aspectRatio.width, motionConfig.aspectRatio.height, motionConfig.fps), ) format.setInteger(MediaFormat.KEY_FRAME_RATE, motionConfig.fps) format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL) @@ -137,7 +137,9 @@ class AndroidVideoGenerator { while (true) { val encoderStatus = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC) when { - encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER -> break + encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER -> { + break + } encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { if (muxerStarted) throw RuntimeException("format changed after muxer start") @@ -156,14 +158,14 @@ class AndroidVideoGenerator { encoderStatus < 0 -> { Log.w( TAG, - "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus" + "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus", ) } else -> { val encodedData = mediaCodec.getOutputBuffer(encoderStatus) ?: throw RuntimeException( - "encoderOutputBuffer $encoderStatus was null" + "encoderOutputBuffer $encoderStatus was null", ) if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { @@ -205,7 +207,7 @@ class AndroidVideoGenerator { fps = motionConfig.fps, initialPresentationTimeUs = presentationTimeUs, audioTrackFormats = audioTrackFormats, - muxerAudioTrackIndices = muxerAudioTrackIndices + muxerAudioTrackIndices = muxerAudioTrackIndices, ) // Now copy audio samples into the muxer (timeline aligned by frames -> microseconds) @@ -244,7 +246,7 @@ class AndroidVideoGenerator { mediaMuxer: MediaMuxer, audioSources: List, fps: Int, - audioTrackIndices: List + audioTrackIndices: List, ) { Log.d(TAG, "muxAudioTracks: adding audio") val bufferSize = 1 * 1024 * 1024 @@ -310,7 +312,7 @@ class AndroidVideoGenerator { fps: Int, initialPresentationTimeUs: Long, audioTrackFormats: List, - muxerAudioTrackIndices: MutableList + muxerAudioTrackIndices: MutableList, ) { var localMuxerStarted = muxerStarted var localVideoTrackIndex = videoTrackIndex @@ -319,7 +321,9 @@ class AndroidVideoGenerator { while (true) { val encoderStatus = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC) when { - encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER -> break + encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER -> { + break + } encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { // This can happen if no encoded output was dequeued earlier. @@ -336,14 +340,17 @@ class AndroidVideoGenerator { localMuxerStarted = true } - encoderStatus < 0 -> Log.w( - TAG, - "unexpected result from encoder.dequeueOutputBuffer (during drain): $encoderStatus" - ) + encoderStatus < 0 -> { + Log.w( + TAG, + "unexpected result from encoder.dequeueOutputBuffer (during drain): $encoderStatus", + ) + } else -> { - val encodedData = mediaCodec.getOutputBuffer(encoderStatus) - ?: throw RuntimeException("encoderOutputBuffer $encoderStatus was null (during drain)") + val encodedData = + mediaCodec.getOutputBuffer(encoderStatus) + ?: throw RuntimeException("encoderOutputBuffer $encoderStatus was null (during drain)") if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { bufferInfo.size = 0 @@ -371,38 +378,47 @@ class AndroidVideoGenerator { } } - private fun calculateBitRate(width: Int, height: Int, frameRate: Int): Int { - return (width * height * frameRate * 0.25).toInt() - } + private fun calculateBitRate( + width: Int, + height: Int, + frameRate: Int, + ): Int = (width * height * frameRate * 0.25).toInt() private fun getBitmapCount( - bitmaps: List = mutableListOf(), inputDir: File? = null - ): Int = if (inputDir != null) { - initBitmapFiles(inputDir) - bitmapFiles?.size ?: 0 - } else { - bitmaps.size - } + bitmaps: List = mutableListOf(), + inputDir: File? = null, + ): Int = + if (inputDir != null) { + initBitmapFiles(inputDir) + bitmapFiles?.size ?: 0 + } else { + bitmaps.size + } private fun getBitmap( - bitmaps: List = mutableListOf(), inputDir: File? = null, index: Int - ): Bitmap? = if (inputDir != null) { - initBitmapFiles(inputDir) + bitmaps: List = mutableListOf(), + inputDir: File? = null, + index: Int, + ): Bitmap? = + if (inputDir != null) { + initBitmapFiles(inputDir) - bitmapFiles?.getOrNull(index)?.let { file -> - BitmapFactory.decodeFile(file.absolutePath) + bitmapFiles?.getOrNull(index)?.let { file -> + BitmapFactory.decodeFile(file.absolutePath) + } + } else { + bitmaps.getOrNull(index) } - } else { - bitmaps.getOrNull(index) - } private fun initBitmapFiles(inputDir: File) { if (bitmapFiles == null) { - bitmapFiles = inputDir.listFiles { file -> - file.extension.lowercase() in listOf("png", "jpg", "jpeg", "webp") - }?.sortedBy { file -> - file.nameWithoutExtension.filter { it.isDigit() }.toIntOrNull() ?: 0 - } + bitmapFiles = + inputDir + .listFiles { file -> + file.extension.lowercase() in listOf("png", "jpg", "jpeg", "webp") + }?.sortedBy { file -> + file.nameWithoutExtension.filter { it.isDigit() }.toIntOrNull() ?: 0 + } } } } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AudioProcessor.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AudioProcessor.kt index 10acb8b0..cb964273 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AudioProcessor.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/infra/AudioProcessor.kt @@ -42,12 +42,20 @@ fun extractWaveformFromFile(file: File): List { if (sampleSize < 0) { codec.queueInputBuffer( - inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM + inputIndex, + 0, + 0, + 0, + MediaCodec.BUFFER_FLAG_END_OF_STREAM, ) isEOS = true } else { codec.queueInputBuffer( - inputIndex, 0, sampleSize, extractor.sampleTime, 0 + inputIndex, + 0, + sampleSize, + extractor.sampleTime, + 0, ) extractor.advance() } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseContourMotionView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseContourMotionView.kt index e2eae4a9..94e9a974 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseContourMotionView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseContourMotionView.kt @@ -14,8 +14,9 @@ open class BaseContourMotionView( override val startFrame: Int, override val endFrame: Int, override val loop: Pair = Pair(0, 0), - override val effects: List = emptyList() -) : ContourLayout(context), MotionView { + override val effects: List = emptyList(), +) : ContourLayout(context), + MotionView { companion object { private const val TAG = "MotionView" } @@ -46,4 +47,4 @@ open class BaseContourMotionView( } override fun getViewBitmap() = this.toBitmap() -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseFrameMotionView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseFrameMotionView.kt index 7d38603a..f20f58f4 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseFrameMotionView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/BaseFrameMotionView.kt @@ -12,77 +12,89 @@ import com.tejpratapsingh.motionlib.core.MotionEffect import com.tejpratapsingh.motionlib.core.MotionView import com.tejpratapsingh.motionlib.core.extensions.toBitmap -abstract class BaseFrameMotionView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr), MotionView { +abstract class BaseFrameMotionView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr), + MotionView { + override var startFrame: Int = 0 + override var endFrame: Int = 0 + override var loop: Pair = Pair(0, 0) - override var startFrame: Int = 0 - override var endFrame: Int = 0 - override var loop: Pair = Pair(0, 0) - - companion object { - private const val TAG = "MotionView" - } - - init { - context.theme.obtainStyledAttributes( - attrs, - R.styleable.BaseFrameMotionView, - defStyleAttr, - 0 - ).apply { - try { - startFrame = getInt(R.styleable.BaseFrameMotionView_startFrame, 0) - endFrame = getInt(R.styleable.BaseFrameMotionView_endFrame, 0) - val loopStart = getInt(R.styleable.BaseFrameMotionView_loopStart, 0) - val loopEnd = getInt(R.styleable.BaseFrameMotionView_loopEnd, 0) - loop = Pair(loopStart, loopEnd) - } finally { - recycle() - } + companion object { + private const val TAG = "MotionView" } - } - @CallSuper - override fun forFrame(frame: Int): MotionView { - if (frame < startFrame) { - visibility = INVISIBLE - return this - } - if (frame > endFrame) { - visibility = INVISIBLE - return this + init { + context.theme + .obtainStyledAttributes( + attrs, + R.styleable.BaseFrameMotionView, + defStyleAttr, + 0, + ).apply { + try { + startFrame = getInt(R.styleable.BaseFrameMotionView_startFrame, 0) + endFrame = getInt(R.styleable.BaseFrameMotionView_endFrame, 0) + val loopStart = getInt(R.styleable.BaseFrameMotionView_loopStart, 0) + val loopEnd = getInt(R.styleable.BaseFrameMotionView_loopEnd, 0) + loop = Pair(loopStart, loopEnd) + } finally { + recycle() + } + } } - visibility = VISIBLE - Log.d(TAG, "forFrame: isVisible: $isVisible") + @CallSuper + override fun forFrame(frame: Int): MotionView { + if (frame < startFrame) { + visibility = INVISIBLE + return this + } + if (frame > endFrame) { + visibility = INVISIBLE + return this + } + visibility = VISIBLE + + Log.d(TAG, "forFrame: isVisible: $isVisible") - for (i in 0 until this.childCount) { - val view = this.getChildAt(i) + for (i in 0 until this.childCount) { + val view = this.getChildAt(i) - if (view is MotionView) { - view.forFrame(frame) + if (view is MotionView) { + view.forFrame(frame) + } } - } - return this - } + return this + } - override fun getViewBitmap() = this.toBitmap() + override fun getViewBitmap() = this.toBitmap() - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val desiredWidth = MotionConfig.aspectRatio.width - val desiredHeight = MotionConfig.aspectRatio.height - setMeasuredDimension(desiredWidth, desiredHeight) - getChildAt(0)?.measure( - MeasureSpec.makeMeasureSpec(desiredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(desiredHeight, MeasureSpec.EXACTLY) - ) - } + override fun onMeasure( + widthMeasureSpec: Int, + heightMeasureSpec: Int, + ) { + val desiredWidth = MotionConfig.aspectRatio.width + val desiredHeight = MotionConfig.aspectRatio.height + setMeasuredDimension(desiredWidth, desiredHeight) + getChildAt(0)?.measure( + MeasureSpec.makeMeasureSpec(desiredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(desiredHeight, MeasureSpec.EXACTLY), + ) + } - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - getChildAt(0)?.layout(0, 0, r - l, b - t) + override fun onLayout( + changed: Boolean, + l: Int, + t: Int, + r: Int, + b: Int, + ) { + getChildAt(0)?.layout(0, 0, r - l, b - t) + } } -} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IComposerView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IComposerView.kt index 10727752..09617baa 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IComposerView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IComposerView.kt @@ -4,4 +4,4 @@ import com.tejpratapsingh.motionlib.core.MotionPlugin interface IComposerView { val plugins: List -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IMotionVideoProducer.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IMotionVideoProducer.kt index 598c67f2..fcfeaa76 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IMotionVideoProducer.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/IMotionVideoProducer.kt @@ -8,9 +8,10 @@ import java.io.File interface IMotionVideoProducer { fun addMotionViewToSequence(motionView: T): MotionVideoProducer where T : MotionView, T : ViewGroup + suspend fun produceVideo( context: Context, outputFile: File, - progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)? = null + progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)? = null, ): File -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionComposerView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionComposerView.kt index 179f876b..a8c91476 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionComposerView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionComposerView.kt @@ -15,9 +15,10 @@ open class MotionComposerView( override val startFrame: Int = 0, override val endFrame: Int = 0, override val plugins: List, - override val loop: Pair = Pair(0, 0) -) : ContourLayout(context), MotionView, IComposerView { - + override val loop: Pair = Pair(0, 0), +) : ContourLayout(context), + MotionView, + IComposerView { override val effects: List = emptyList() companion object { @@ -41,17 +42,17 @@ open class MotionComposerView( return this } - fun runEffects(view: MotionView, frame: Int) { - return view.effects.forEach { effect -> - effect.forFrame(frame) - } + fun runEffects( + view: MotionView, + frame: Int, + ) = view.effects.forEach { effect -> + effect.forFrame(frame) } - override fun getViewBitmap(): Bitmap { - return toBitmap().let { + override fun getViewBitmap(): Bitmap = + toBitmap().let { plugins.fold(it) { acc, plugin -> plugin.apply(acc) } } - } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt index 924e0771..d9f89ade 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt @@ -18,7 +18,7 @@ open class MotionVideoProducer( val motionConfig: MotionConfig, val videoProducerAdapter: VideoProducerAdapter, val motionComposerView: MotionComposerView, - val motionAudio: List = emptyList() + val motionAudio: List = emptyList(), ) : IMotionVideoProducer { var totalFrames: Int = 0 private set @@ -37,10 +37,12 @@ open class MotionVideoProducer( context = context, motionConfig = config, videoProducerAdapter = videoProducerAdapter, - motionComposerView = MotionComposerView( - context = context, plugins = plugins - ), - motionAudio = motionAudio + motionComposerView = + MotionComposerView( + context = context, + plugins = plugins, + ), + motionAudio = motionAudio, ).also { MotionConfig.aspectRatio = config.aspectRatio MotionConfig.fps = config.fps @@ -51,11 +53,16 @@ open class MotionVideoProducer( override fun addMotionViewToSequence(motionView: T): MotionVideoProducer where T : MotionView, T : ViewGroup { totalFrames = maxOf(totalFrames, motionView.endFrame) motionComposerView.apply { - motionView.layoutBy(x = centerHorizontallyTo { - parent.centerX() - }, y = centerVerticallyTo { - parent.centerY() - }) + motionView.layoutBy( + x = + centerHorizontallyTo { + parent.centerX() + }, + y = + centerVerticallyTo { + parent.centerY() + }, + ) } return this } @@ -63,22 +70,24 @@ open class MotionVideoProducer( override suspend fun produceVideo( context: Context, outputFile: File, - progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)? - ): File = withContext(Dispatchers.IO) { // Use Dispatchers.Default for CPU-bound work - if (outputFile.exists()) { - outputFile.delete() - } + progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)?, + ): File = + withContext(Dispatchers.IO) { + // Use Dispatchers.Default for CPU-bound work + if (outputFile.exists()) { + outputFile.delete() + } - videoProducerAdapter.produceVideo( - context = context, - motionConfig = motionConfig, - motionComposerView = motionComposerView, - motionAudio = motionAudio, - totalFrames = totalFrames, - outputFile = outputFile, - progressListener = progressListener - ) + videoProducerAdapter.produceVideo( + context = context, + motionConfig = motionConfig, + motionComposerView = motionComposerView, + motionAudio = motionAudio, + totalFrames = totalFrames, + outputFile = outputFile, + progressListener = progressListener, + ) - outputFile - } -} \ No newline at end of file + outputFile + } +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/OrientedMotionView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/OrientedMotionView.kt index ace2ad26..c559b1a9 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/OrientedMotionView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/OrientedMotionView.kt @@ -5,5 +5,5 @@ import android.content.Context open class OrientedMotionView( context: Context, startFrame: Int, - endFrame: Int -) : BaseContourMotionView(context, startFrame, endFrame) \ No newline at end of file + endFrame: Int, +) : BaseContourMotionView(context, startFrame, endFrame) diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/MotionVideoPlayer.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/MotionVideoPlayer.kt index e34e21e3..11730745 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/MotionVideoPlayer.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/MotionVideoPlayer.kt @@ -17,9 +17,10 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch -class MotionVideoPlayer(context: Context, private val motionVideoProducer: MotionVideoProducer) : - ContourLayout(context) { - +class MotionVideoPlayer( + context: Context, + private val motionVideoProducer: MotionVideoProducer, +) : ContourLayout(context) { companion object { private const val TAG = "MotionVideoPlayer" } @@ -33,75 +34,93 @@ class MotionVideoPlayer(context: Context, private val motionVideoProducer: Motio private val activePlayers = mutableMapOf() - val seekBar: SeekBar = SeekBar(context).apply { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - min = 1 - } // Start from 1 to avoid confusion with frame 0 - max = motionVideoProducer.totalFrames + val seekBar: SeekBar = + SeekBar(context).apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + min = 1 + } // Start from 1 to avoid confusion with frame 0 + max = motionVideoProducer.totalFrames + + setOnSeekBarChangeListener( + object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged( + seekBar: SeekBar?, + progress: Int, + fromUser: Boolean, + ) { + if (fromUser) { // Only update preview if change is from user interaction + motionVideoProducer.motionComposerView.forFrame(progress) + } + } - setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - if (fromUser) { // Only update preview if change is from user interaction - motionVideoProducer.motionComposerView.forFrame(progress) - } - } + override fun onStartTrackingTouch(seekBar: SeekBar?) { + // Optional: Pause playback when user starts dragging + if (isPlaying) { + pausePlayback() + } + } - override fun onStartTrackingTouch(seekBar: SeekBar?) { - // Optional: Pause playback when user starts dragging + override fun onStopTrackingTouch(seekBar: SeekBar?) { + // Optional: Resume playback or leave it paused + } + }, + ) + } + + private val playPauseButton: ImageButton = + ImageButton(context).apply { + setImageResource(android.R.drawable.ic_media_play) + setOnClickListener { if (isPlaying) { pausePlayback() + } else { + startPlayback() } } - - override fun onStopTrackingTouch(seekBar: SeekBar?) { - // Optional: Resume playback or leave it paused - } - }) - } - - private val playPauseButton: ImageButton = ImageButton(context).apply { - setImageResource(android.R.drawable.ic_media_play) - setOnClickListener { - if (isPlaying) { - pausePlayback() - } else { - startPlayback() - } } - } - private val controlsLayout: LinearLayoutCompat = LinearLayoutCompat(context).apply { - orientation = LinearLayoutCompat.HORIZONTAL - gravity = android.view.Gravity.CENTER_VERTICAL // Center items in controls - } + private val controlsLayout: LinearLayoutCompat = + LinearLayoutCompat(context).apply { + orientation = LinearLayoutCompat.HORIZONTAL + gravity = android.view.Gravity.CENTER_VERTICAL // Center items in controls + } - private val previewLayout: LinearLayoutCompat = LinearLayoutCompat(context).apply { - orientation = LinearLayoutCompat.VERTICAL - gravity = android.view.Gravity.CENTER // Center preview - } + private val previewLayout: LinearLayoutCompat = + LinearLayoutCompat(context).apply { + orientation = LinearLayoutCompat.VERTICAL + gravity = android.view.Gravity.CENTER // Center preview + } init { controlsLayout.addView(playPauseButton) controlsLayout.addView(seekBar) - val seekBarParams = LinearLayoutCompat.LayoutParams( - 0, LinearLayoutCompat.LayoutParams.WRAP_CONTENT - ).apply { - weight = 1f - } + val seekBarParams = + LinearLayoutCompat + .LayoutParams( + 0, + LinearLayoutCompat.LayoutParams.WRAP_CONTENT, + ).apply { + weight = 1f + } seekBar.layoutParams = seekBarParams controlsLayout.layoutBy( x = leftTo { parent.left() }.rightTo { parent.right() }, - y = bottomTo { parent.bottom() }) + y = bottomTo { parent.bottom() }, + ) previewLayout.addView(motionVideoProducer.motionComposerView) previewLayout.layoutBy( x = leftTo { parent.left() }.rightTo { parent.right() }, - y = topTo { parent.top() }.bottomTo { controlsLayout.top() }) + y = topTo { parent.top() }.bottomTo { controlsLayout.top() }, + ) } - private fun startAudioIfNeeded(frame: Int, motionAudios: List) { + private fun startAudioIfNeeded( + frame: Int, + motionAudios: List, + ) { motionAudios.forEach { audio -> val shouldPlay = frame in audio.delayFrame..audio.endFrame val player = activePlayers[audio] @@ -109,11 +128,12 @@ class MotionVideoPlayer(context: Context, private val motionVideoProducer: Motio if (shouldPlay) { if (player == null) { // First time: create player - val mediaPlayer = android.media.MediaPlayer().apply { - setDataSource(audio.file.absolutePath) - prepare() - start() - } + val mediaPlayer = + android.media.MediaPlayer().apply { + setDataSource(audio.file.absolutePath) + prepare() + start() + } activePlayers[audio] = mediaPlayer } else if (!player.isPlaying) { // Resume instead of restarting @@ -155,28 +175,29 @@ class MotionVideoPlayer(context: Context, private val motionVideoProducer: Motio playPauseButton.setImageResource(android.R.drawable.ic_media_pause) // Launch a new coroutine for playback - playbackJob = scope.launch { - while (isPlaying) { - var currentProgress = seekBar.progress - if (currentProgress < seekBar.max) { - currentProgress++ - seekBar.progress = currentProgress - motionVideoProducer.motionComposerView.forFrame(currentProgress) - - // 🔊 Check if we should play audio - startAudioIfNeeded( - frame = currentProgress, - motionAudios = motionVideoProducer.motionAudio // you need to expose this - ) - - delay(playbackDelayMs) - } else { - seekBar.progress = 0 - motionVideoProducer.motionComposerView.forFrame(0) - stopAllAudio() + playbackJob = + scope.launch { + while (isPlaying) { + var currentProgress = seekBar.progress + if (currentProgress < seekBar.max) { + currentProgress++ + seekBar.progress = currentProgress + motionVideoProducer.motionComposerView.forFrame(currentProgress) + + // 🔊 Check if we should play audio + startAudioIfNeeded( + frame = currentProgress, + motionAudios = motionVideoProducer.motionAudio, // you need to expose this + ) + + delay(playbackDelayMs) + } else { + seekBar.progress = 0 + motionVideoProducer.motionComposerView.forFrame(0) + stopAllAudio() + } } } - } } private fun pausePlayback() { diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/CutoutTextView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/CutoutTextView.kt index d9e3d469..36f30529 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/CutoutTextView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/CutoutTextView.kt @@ -9,38 +9,39 @@ import android.graphics.PorterDuffXfermode import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView -internal class CutoutTextView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0 -) : AppCompatTextView(context, attrs, defStyle) { - - private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG) - private val porterDuffMode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) - - override fun onDraw(canvas: Canvas) { - // Draw the background image first - val bgDrawable = background - bgDrawable?.setBounds(0, 0, width, height) - bgDrawable?.draw(canvas) - - // Create a new layer for cutout - val saveCount = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null) - - // Fill with black overlay - canvas.drawColor(Color.BLACK) - - // Cut out text - textPaint.typeface = typeface - textPaint.textSize = textSize - textPaint.textAlign = Paint.Align.CENTER - textPaint.xfermode = porterDuffMode - - val xPos = width / 2f - val yPos = height / 2f - (textPaint.descent() + textPaint.ascent()) / 2 - canvas.drawText(text.toString(), xPos, yPos, textPaint) - - textPaint.xfermode = null - canvas.restoreToCount(saveCount) +internal class CutoutTextView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, + ) : AppCompatTextView(context, attrs, defStyle) { + private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val porterDuffMode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + + override fun onDraw(canvas: Canvas) { + // Draw the background image first + val bgDrawable = background + bgDrawable?.setBounds(0, 0, width, height) + bgDrawable?.draw(canvas) + + // Create a new layer for cutout + val saveCount = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null) + + // Fill with black overlay + canvas.drawColor(Color.BLACK) + + // Cut out text + textPaint.typeface = typeface + textPaint.textSize = textSize + textPaint.textAlign = Paint.Align.CENTER + textPaint.xfermode = porterDuffMode + + val xPos = width / 2f + val yPos = height / 2f - (textPaint.descent() + textPaint.ascent()) / 2 + canvas.drawText(text.toString(), xPos, yPos, textPaint) + + textPaint.xfermode = null + canvas.restoreToCount(saveCount) + } } -} \ No newline at end of file diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/BaseAudioWaveformView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/BaseAudioWaveformView.kt index 0f860ddd..94dd19e2 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/BaseAudioWaveformView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/BaseAudioWaveformView.kt @@ -4,21 +4,24 @@ import android.content.Context import android.graphics.Paint import com.tejpratapsingh.motionlib.core.motion.BaseContourMotionView -open class BaseAudioWaveformView(context: Context, - override var startFrame: Int, - override var endFrame: Int +open class BaseAudioWaveformView( + context: Context, + override var startFrame: Int, + override var endFrame: Int, ) : BaseContourMotionView(context, startFrame, endFrame) { protected var currentFrame: Int = 0 - protected val paint = Paint().apply { - color = 0xFF009688.toInt() // Teal spikes - strokeWidth = 3f - isAntiAlias = true - } + protected val paint = + Paint().apply { + color = 0xFF009688.toInt() // Teal spikes + strokeWidth = 3f + isAntiAlias = true + } - protected val cursorPaint = Paint().apply { - color = 0xFFFF5722.toInt() // Orange playback cursor - strokeWidth = 5f - isAntiAlias = true - } -} \ No newline at end of file + protected val cursorPaint = + Paint().apply { + color = 0xFFFF5722.toInt() // Orange playback cursor + strokeWidth = 5f + isAntiAlias = true + } +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/CircularAudioWaveformView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/CircularAudioWaveformView.kt index 2d0176e4..2db8b704 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/CircularAudioWaveformView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/CircularAudioWaveformView.kt @@ -12,9 +12,8 @@ class CircularAudioWaveformView( context: Context, private val amplitudes: List = emptyList(), override var startFrame: Int, - override var endFrame: Int + override var endFrame: Int, ) : BaseAudioWaveformView(context, startFrame, endFrame) { - override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (amplitudes.isEmpty() || startFrame >= endFrame) return @@ -49,4 +48,4 @@ class CircularAudioWaveformView( invalidate() return this } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/RadialAudioWaveformView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/RadialAudioWaveformView.kt index e6844a1a..6e8d739f 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/RadialAudioWaveformView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/audio/RadialAudioWaveformView.kt @@ -14,9 +14,8 @@ class RadialAudioWaveformView( context: Context, private val amplitudes: List = emptyList(), override var startFrame: Int, - override var endFrame: Int + override var endFrame: Int, ) : BaseAudioWaveformView(context, startFrame, endFrame) { - override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (amplitudes.isEmpty() || startFrame >= endFrame) return @@ -56,4 +55,4 @@ class RadialAudioWaveformView( invalidate() return this } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/background/GradientView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/background/GradientView.kt index e1f3a378..b9546f9c 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/background/GradientView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/background/GradientView.kt @@ -18,7 +18,7 @@ import com.tejpratapsingh.motionlib.core.motion.OrientedMotionView enum class Orientation { HORIZONTAL, VERTICAL, - CIRCULAR + CIRCULAR, } class GradientView( @@ -26,19 +26,20 @@ class GradientView( startFrame: Int, endFrame: Int, private val orientation: Orientation, - private val colors: IntArray + private val colors: IntArray, ) : OrientedMotionView( - context = context, - startFrame = startFrame, - endFrame = endFrame -) { + context = context, + startFrame = startFrame, + endFrame = endFrame, + ) { private companion object { // const val TAG = "GradientView" // For logging if needed } - private val paint: Paint = Paint().apply { - isAntiAlias = true // Good practice for smoother gradients - } + private val paint: Paint = + Paint().apply { + isAntiAlias = true // Good practice for smoother gradients + } private var currentFrame: Int = 0 private val interpolator: Interpolators = @@ -66,7 +67,12 @@ class GradientView( } } - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + override fun onSizeChanged( + w: Int, + h: Int, + oldw: Int, + oldh: Int, + ) { super.onSizeChanged(w, h, oldw, oldh) // If valueRange depends on the view's actual dimensions: /* @@ -75,7 +81,7 @@ class GradientView( Orientation.VERTICAL -> Pair(first = 0f, second = h.toFloat().coerceAtLeast(1f)) Orientation.HORIZONTAL -> Pair(first = 0f, second = w.toFloat().coerceAtLeast(1f)) } - */ + */ gradientShader = null // Invalidate shader if size changes affect it } @@ -98,46 +104,54 @@ class GradientView( // Log.d(TAG, "onDraw: called : $currentFrame, width: $width, height: $height, valueRange: $valueRange") // } - val interpolatedValue: Float = MotionInterpolator.interpolateForRange( - interpolator = interpolator, - currentFrame = currentFrame, - frameRange = frameRange, - valueRange = valueRange - ) + val interpolatedValue: Float = + MotionInterpolator.interpolateForRange( + interpolator = interpolator, + currentFrame = currentFrame, + frameRange = frameRange, + valueRange = valueRange, + ) // Only recreate shader if necessary (value changed or not created yet) if (gradientShader == null || interpolatedValue != lastInterpolatedValue) { lastInterpolatedValue = interpolatedValue - gradientShader = when (orientation) { - Orientation.CIRCULAR -> RadialGradient( - (width / 2).toFloat(), - (height / 2).toFloat(), - interpolatedValue.coerceAtLeast(0.1f), // Ensure radius is positive - colors, - null, // Positions: null means evenly distributed - Shader.TileMode.CLAMP - ) - - Orientation.VERTICAL -> LinearGradient( - 0f, - 0f, - 0f, - interpolatedValue.coerceAtLeast(0.1f), // Ensure height is positive - colors, - null, - Shader.TileMode.CLAMP - ) - - Orientation.HORIZONTAL -> LinearGradient( - 0f, - 0f, - interpolatedValue.coerceAtLeast(0.1f), // Ensure width is positive - 0f, - colors, - null, - Shader.TileMode.CLAMP - ) - } + gradientShader = + when (orientation) { + Orientation.CIRCULAR -> { + RadialGradient( + (width / 2).toFloat(), + (height / 2).toFloat(), + interpolatedValue.coerceAtLeast(0.1f), // Ensure radius is positive + colors, + null, // Positions: null means evenly distributed + Shader.TileMode.CLAMP, + ) + } + + Orientation.VERTICAL -> { + LinearGradient( + 0f, + 0f, + 0f, + interpolatedValue.coerceAtLeast(0.1f), // Ensure height is positive + colors, + null, + Shader.TileMode.CLAMP, + ) + } + + Orientation.HORIZONTAL -> { + LinearGradient( + 0f, + 0f, + interpolatedValue.coerceAtLeast(0.1f), // Ensure width is positive + 0f, + colors, + null, + Shader.TileMode.CLAMP, + ) + } + } paint.shader = gradientShader } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/container/RotatingMotionView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/container/RotatingMotionView.kt index f8088ae9..87f5234a 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/container/RotatingMotionView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/container/RotatingMotionView.kt @@ -10,20 +10,22 @@ class RotatingMotionView( startFrame: Int, endFrame: Int, view: View, - private val degreePerSecond: Float = 6f + private val degreePerSecond: Float = 6f, ) : BaseContourMotionView(context, startFrame, endFrame) { - init { view.layoutBy( - x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, y = topTo { - parent.top() - }.bottomTo { - parent.bottom() - } + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }.bottomTo { + parent.bottom() + }, ) } @@ -39,4 +41,4 @@ class RotatingMotionView( return this } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TransparentTextView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TransparentTextView.kt index a28f86e7..7ba619f4 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TransparentTextView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TransparentTextView.kt @@ -6,14 +6,17 @@ import com.tejpratapsingh.motionlib.ui.custom.CutoutTextView import com.tejpratapsingh.motionlib.ui.custom.text.abstract.AbstractMotionTextView class TransparentTextView( - context: Context, private val text: String, startFrame: Int, endFrame: Int + context: Context, + private val text: String, + startFrame: Int, + endFrame: Int, ) : AbstractMotionTextView( - context, - text, - startFrame, - endFrame, - textView = CutoutTextView(context) -) { + context, + text, + startFrame, + endFrame, + textView = CutoutTextView(context), + ) { private val TAG by lazy { "TransparentTextView" } @@ -25,4 +28,4 @@ class TransparentTextView( return this } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TypeWriterTextView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TypeWriterTextView.kt index e77bff2e..0f47b7ed 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TypeWriterTextView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/TypeWriterTextView.kt @@ -19,7 +19,7 @@ class TypeWriterTextView( private val text: String, startFrame: Int, endFrame: Int, - textView: AppCompatTextView = CutoutTextView(context) + textView: AppCompatTextView = CutoutTextView(context), ) : AbstractMotionTextView(context, text, startFrame, endFrame, textView) { private val TAG by lazy { "TypeWriterTextView" @@ -28,12 +28,14 @@ class TypeWriterTextView( override fun forFrame(frame: Int): MotionView { super.forFrame(frame) - val visibleCharsCount: Int = MotionInterpolator.interpolateForRange( - Interpolators(Easings.LINEAR), - frame, - Pair(startFrame, endFrame), - Pair(0f, text.length.toFloat()) - ).toInt() + val visibleCharsCount: Int = + MotionInterpolator + .interpolateForRange( + Interpolators(Easings.LINEAR), + frame, + Pair(startFrame, endFrame), + Pair(0f, text.length.toFloat()), + ).toInt() Log.d(TAG, "visibleCharsCount: $visibleCharsCount") @@ -42,10 +44,10 @@ class TypeWriterTextView( ForegroundColorSpan(Color.TRANSPARENT), maxOf(0, visibleCharsCount), text.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, ) textView.text = spannableString textView.invalidate() return this } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordBlinkTextView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordBlinkTextView.kt index aad32ab0..4ccfbc2b 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordBlinkTextView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordBlinkTextView.kt @@ -18,7 +18,7 @@ class WordBlinkTextView( text: String, startFrame: Int = 0, endFrame: Int = -1, - textView: AppCompatTextView = CutoutTextView(context) + textView: AppCompatTextView = CutoutTextView(context), ) : AbstractMotionTextView(context, text, startFrame, endFrame, textView) { private val TAG by lazy { "WordBlinkTextView" @@ -34,19 +34,21 @@ class WordBlinkTextView( override fun forFrame(frame: Int): MotionView { super.forFrame(frame) - val visibleWordCount: Int = MotionInterpolator.interpolateForRange( - Interpolators(Easings.LINEAR), - frame, - Pair(startFrame, endFrame), - Pair(0f, wordCount.toFloat()) - ).toInt() + val visibleWordCount: Int = + MotionInterpolator + .interpolateForRange( + Interpolators(Easings.LINEAR), + frame, + Pair(startFrame, endFrame), + Pair(0f, wordCount.toFloat()), + ).toInt() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { (textView as TextView).setAutoSizeTextTypeUniformWithConfiguration( 12, 100, 1, - TypedValue.COMPLEX_UNIT_SP + TypedValue.COMPLEX_UNIT_SP, ) } @@ -57,4 +59,4 @@ class WordBlinkTextView( return this } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordWriterTextView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordWriterTextView.kt index d21ecc6f..7de84bb3 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordWriterTextView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/WordWriterTextView.kt @@ -19,7 +19,7 @@ class WordWriterTextView( private val text: String, startFrame: Int = 0, endFrame: Int = -1, - textView: AppCompatTextView = CutoutTextView(context) + textView: AppCompatTextView = CutoutTextView(context), ) : AbstractMotionTextView(context, text, startFrame, endFrame, textView) { private val TAG by lazy { "WordWriterTextView" @@ -31,12 +31,14 @@ class WordWriterTextView( override fun forFrame(frame: Int): MotionView { super.forFrame(frame) - val visibleWordCount: Int = MotionInterpolator.interpolateForRange( - Interpolators(Easings.LINEAR), - frame, - Pair(startFrame, endFrame), - Pair(0f, wordCount.toFloat()) - ).toInt() + val visibleWordCount: Int = + MotionInterpolator + .interpolateForRange( + Interpolators(Easings.LINEAR), + frame, + Pair(startFrame, endFrame), + Pair(0f, wordCount.toFloat()), + ).toInt() Log.d(TAG, "visibleWordCount: $visibleWordCount") val visibleCharacters = wordArray.subList(0, visibleWordCount).joinToString(" ").length @@ -46,11 +48,11 @@ class WordWriterTextView( ForegroundColorSpan(Color.TRANSPARENT), maxOf(0, visibleCharacters), text.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, ) textView.text = spannableString textView.invalidate() return this } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt index 87d9d921..3f970331 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt @@ -11,9 +11,8 @@ abstract class AbstractMotionTextView( startFrame: Int, endFrame: Int, val textView: AppCompatTextView, - fontUrl: String? = null + fontUrl: String? = null, ) : BaseContourMotionView(context, startFrame, endFrame) { - init { textView.apply { if (fontUrl != null) { @@ -21,15 +20,20 @@ abstract class AbstractMotionTextView( } } - textView.layoutBy(x = leftTo { - parent.left() - }.rightTo { - parent.right() - }, y = topTo { - parent.top() - }.bottomTo { - parent.bottom() - }) + textView.layoutBy( + x = + leftTo { + parent.left() + }.rightTo { + parent.right() + }, + y = + topTo { + parent.top() + }.bottomTo { + parent.bottom() + }, + ) textView.text = text } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/VideoFrameView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/VideoFrameView.kt index e9b6be8c..1f6653a0 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/VideoFrameView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/VideoFrameView.kt @@ -12,22 +12,29 @@ import com.tejpratapsingh.motionlib.utils.getVideoFpsWithRetriever import kotlin.math.roundToLong class VideoFrameView( - context: Context, videoUri: Uri, startFrame: Int, endFrame: Int + context: Context, + videoUri: Uri, + startFrame: Int, + endFrame: Int, ) : BaseContourMotionView(context, startFrame, endFrame) { - val fps = getVideoFpsWithRetriever(context, videoUri) ?: 30F - val videoBitmaps = extractAllVideoFrames( - context = context, videoUri = videoUri, frameIntervalUs = (1_000_000 / fps).roundToLong() - ) + val videoBitmaps = + extractAllVideoFrames( + context = context, + videoUri = videoUri, + frameIntervalUs = (1_000_000 / fps).roundToLong(), + ) - private val imageView = ImageView(context).apply { - scaleType = ImageView.ScaleType.CENTER_CROP - } + private val imageView = + ImageView(context).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + } init { imageView.layoutBy( x = leftTo { parent.left() }.rightTo { parent.right() }, - y = topTo { parent.top() }.bottomTo { parent.bottom() }) + y = topTo { parent.top() }.bottomTo { parent.bottom() }, + ) imageView.scaleType = ImageView.ScaleType.CENTER_CROP contourHeightOf { @@ -44,10 +51,10 @@ class VideoFrameView( super.forFrame(frame) currentFrameBitmap = videoBitmaps.getOrNull(frame - startFrame) ?: videoBitmaps.last() imageView.setImageBitmap( - currentFrameBitmap + currentFrameBitmap, ) return this } override fun getViewBitmap(): Bitmap = currentFrameBitmap -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SlideRightToLeftEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SlideRightToLeftEffect.kt index 960fbb65..e3011ac4 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SlideRightToLeftEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SlideRightToLeftEffect.kt @@ -10,7 +10,7 @@ import com.tejpratapsingh.motionlib.core.animation.MotionInterpolator class SlideRightToLeftEffect( override val motionView: MotionView, override val startFrame: Int, - override val endFrame: Int + override val endFrame: Int, ) : MotionEffect { private val TAG by lazy { "SlideRightToLeftEffect" @@ -22,12 +22,13 @@ class SlideRightToLeftEffect( val view = motionView as View - val progress = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(0f, 1f) - ) + val progress = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(0f, 1f), + ) val width = view.width.toFloat() @@ -36,4 +37,4 @@ class SlideRightToLeftEffect( return motionView } -} \ No newline at end of file +} diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/TextViewUtil.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/TextViewUtil.kt index fefa45f3..724df474 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/TextViewUtil.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/TextViewUtil.kt @@ -10,8 +10,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import java.io.File -fun TextView.getWebFont(url: String): Typeface? { - return runBlocking(Dispatchers.IO) { +fun TextView.getWebFont(url: String): Typeface? = + runBlocking(Dispatchers.IO) { val client = HttpClient(CIO) try { val response = client.get(url).body() @@ -25,4 +25,3 @@ fun TextView.getWebFont(url: String): Typeface? { client.close() } } -} \ No newline at end of file diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/VideoUtil.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/VideoUtil.kt index 72fd6350..b158f866 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/VideoUtil.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/VideoUtil.kt @@ -17,7 +17,7 @@ import android.net.Uri fun extractAllVideoFrames( context: Context, videoUri: Uri, - frameIntervalUs: Long + frameIntervalUs: Long, ): List { val retriever = MediaMetadataRetriever() val frames = mutableListOf() @@ -27,18 +27,22 @@ fun extractAllVideoFrames( retriever.setDataSource(context, videoUri) // 2. Retrieve video duration (in microseconds) - val durationUs = retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_DURATION - )?.toLongOrNull()?.times(1000) ?: 0L + val durationUs = + retriever + .extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION, + )?.toLongOrNull() + ?.times(1000) ?: 0L // 3. Iterate through timestamps from 0 to duration, stepping by frameIntervalUs var timeUs = 0L while (timeUs < durationUs) { // OPTIONALLY: Use OPTION_CLOSEST or OPTION_CLOSEST_SYNC - val bitmap = retriever.getFrameAtTime( - timeUs, - MediaMetadataRetriever.OPTION_CLOSEST - ) + val bitmap = + retriever.getFrameAtTime( + timeUs, + MediaMetadataRetriever.OPTION_CLOSEST, + ) bitmap?.let { frames.add(it) } timeUs += frameIntervalUs } @@ -56,14 +60,18 @@ fun extractAllVideoFrames( * @param videoUri Uri of the video (content://, file://, etc.). * @return FPS as a Float, or null if unavailable. */ -fun getVideoFpsWithRetriever(context: Context, videoUri: Uri): Float? { +fun getVideoFpsWithRetriever( + context: Context, + videoUri: Uri, +): Float? { val retriever = MediaMetadataRetriever() return try { retriever.setDataSource(context, videoUri) // METADATA_KEY_CAPTURE_FRAMERATE introduced in API 24 - retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE - )?.toFloatOrNull() + retriever + .extractMetadata( + MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE, + )?.toFloatOrNull() } finally { retriever.release() } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt index dd3e5a76..8e7ca3cc 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt @@ -14,9 +14,8 @@ import java.io.File abstract class MotionWorker( appContext: Context, - workerParams: WorkerParameters + workerParams: WorkerParameters, ) : CoroutineWorker(appContext, workerParams) { - companion object { private const val TAG = "MotionWorker" const val PROGRESS_KEY = "progress" @@ -38,32 +37,35 @@ abstract class MotionWorker( override suspend fun doWork(): Result { Log.d(TAG, "Worker ${this.id}: Starting video generation.") return try { - val videoFile: File = generateVideo( - motionVideoProducer = mMotionVideoProducer, - progressListener = { progress, currentBitmap -> - // Report progress to WorkManager - val progressData = workDataOf( - PROGRESS_KEY to progress, - TOTAL_FRAMES_KEY to mMotionVideoProducer.totalFrames - ) - setProgressAsync(progressData) + val videoFile: File = + generateVideo( + motionVideoProducer = mMotionVideoProducer, + progressListener = { progress, currentBitmap -> + // Report progress to WorkManager + val progressData = + workDataOf( + PROGRESS_KEY to progress, + TOTAL_FRAMES_KEY to mMotionVideoProducer.totalFrames, + ) + setProgressAsync(progressData) - // Call the abstract onProgress for more specific handling - onProgress( - totalFrames = mMotionVideoProducer.totalFrames, - currentProgress = progress, // Renamed for clarity - bitmap = currentBitmap - ) - } - ) + // Call the abstract onProgress for more specific handling + onProgress( + totalFrames = mMotionVideoProducer.totalFrames, + currentProgress = progress, // Renamed for clarity + bitmap = currentBitmap, + ) + }, + ) this.onCompleted(videoFile = videoFile) Log.d( TAG, - "Worker ${this.workId}: Video generation successful: ${videoFile.absolutePath}" - ) - val outputData = workDataOf( - KEY_OUTPUT_VIDEO_URI to videoFile.toUri().toString() + "Worker ${this.workId}: Video generation successful: ${videoFile.absolutePath}", ) + val outputData = + workDataOf( + KEY_OUTPUT_VIDEO_URI to videoFile.toUri().toString(), + ) Result.success(outputData) } catch (e: Exception) { Log.e(TAG, "Worker ${this.workId}: Error during video generation.", e) @@ -86,7 +88,11 @@ abstract class MotionWorker( * @param currentProgress The number of frames processed so far. * @param bitmap The current frame/bitmap being processed. */ - abstract fun onProgress(totalFrames: Int, currentProgress: Int, bitmap: Bitmap) + abstract fun onProgress( + totalFrames: Int, + currentProgress: Int, + bitmap: Bitmap, + ) /** * Called when the video generation is completed successfully. @@ -108,24 +114,25 @@ abstract class MotionWorker( private suspend fun generateVideo( motionVideoProducer: MotionVideoProducer, - progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)? + progressListener: ((progress: Int, bitmap: Bitmap) -> Unit)?, ): File = withContext(Dispatchers.IO) { - val outputFile = File.createTempFile( - "motion_video_out_", // More descriptive prefix - ".mp4", - applicationContext.cacheDir // Use cacheDir for temp files that can be cleared - ) + val outputFile = + File.createTempFile( + "motion_video_out_", // More descriptive prefix + ".mp4", + applicationContext.cacheDir, // Use cacheDir for temp files that can be cleared + ) Log.d( TAG, - "Worker ${this@MotionWorker.workId}: Generating video at ${outputFile.absolutePath}" + "Worker ${this@MotionWorker.workId}: Generating video at ${outputFile.absolutePath}", ) // Assuming produceVideo handles its own exceptions or lets them propagate return@withContext motionVideoProducer.produceVideo( context = applicationContext, outputFile = outputFile, - progressListener = progressListener + progressListener = progressListener, ) } } diff --git a/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/ExampleUnitTest.kt b/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/ExampleUnitTest.kt index 4fddccf9..c5a1a975 100644 --- a/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/ExampleUnitTest.kt +++ b/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/PyTorchImageProcessor.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/PyTorchImageProcessor.kt index 6694909a..e6daaf49 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/PyTorchImageProcessor.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/PyTorchImageProcessor.kt @@ -16,10 +16,9 @@ object PyTorchImageProcessor { */ val backgroundRemoverPlugin: MotionPlugin by lazy { object : MotionPlugin { - override fun apply(input: Bitmap): Bitmap { - return backgroundRemover.clearBackground(input) + override fun apply(input: Bitmap): Bitmap = + backgroundRemover.clearBackground(input) ?: throw IllegalStateException("Super Resolution processing failed") - } } } @@ -29,10 +28,9 @@ object PyTorchImageProcessor { */ val superResolutionPlugin: MotionPlugin by lazy { object : MotionPlugin { - override fun apply(input: Bitmap): Bitmap { - return superResolutionProcessor.upscaleImage(input) + override fun apply(input: Bitmap): Bitmap = + superResolutionProcessor.upscaleImage(input) ?: throw IllegalStateException("Super Resolution processing failed") - } } } diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/common/ModelTypes.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/common/ModelTypes.kt index 2d80d3c2..310585e8 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/common/ModelTypes.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/common/ModelTypes.kt @@ -1,6 +1,8 @@ package com.tejpratapsingh.motionlib.pytorch.common -enum class ModelTypes(val fileName: String) { +enum class ModelTypes( + val fileName: String, +) { U2NET("u2net.ptl"), - NINASR("ninasr_b0_2x.ptl") -} \ No newline at end of file + NINASR("ninasr_b0_2x.ptl"), +} diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/RemoveBg.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/RemoveBg.kt index 6706f289..0c2dae74 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/RemoveBg.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/RemoveBg.kt @@ -16,14 +16,16 @@ import org.pytorch.LiteModuleLoader import org.pytorch.Module import org.pytorch.torchvision.TensorImageUtils -class RemoveBg(context: Context) : Remover { - - private var module: Module = LiteModuleLoader.load( - assetFilePath( - context, - ModelTypes.U2NET.fileName +class RemoveBg( + context: Context, +) : Remover { + private var module: Module = + LiteModuleLoader.load( + assetFilePath( + context, + ModelTypes.U2NET.fileName, + ), ) - ) private val maskPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) private val size = 320 @@ -38,7 +40,10 @@ class RemoveBg(context: Context) : Remover { return removeBackground(mutableImage) } - override fun getMaskedImage(input: Bitmap, mask: Bitmap): Bitmap { + override fun getMaskedImage( + input: Bitmap, + mask: Bitmap, + ): Bitmap { val result = createBitmap(mask.width, mask.height) val mCanvas = Canvas(result) @@ -52,15 +57,15 @@ class RemoveBg(context: Context) : Remover { val height = input.height val scaledBitmap = input.scale(size, size) - val inputTensor = TensorImageUtils.bitmapToFloat32Tensor( - scaledBitmap, - TensorImageUtils.TORCHVISION_NORM_MEAN_RGB, - TensorImageUtils.TORCHVISION_NORM_STD_RGB - ) + val inputTensor = + TensorImageUtils.bitmapToFloat32Tensor( + scaledBitmap, + TensorImageUtils.TORCHVISION_NORM_MEAN_RGB, + TensorImageUtils.TORCHVISION_NORM_STD_RGB, + ) val outputTensor = module.forward(IValue.from(inputTensor)).toTuple() val arr = outputTensor[0].toTensor().dataAsFloatArray val scaledMask = NetUtils.convertArrayToBitmap(arr, size, size)?.scale(width, height) return scaledMask?.let { getMaskedImage(input, it) } } - -} \ No newline at end of file +} diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/Remover.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/Remover.kt index 22d43f69..5a6bd210 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/Remover.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/removebg/Remover.kt @@ -4,9 +4,10 @@ package com.tejpratapsingh.motionlib.pytorch.removebg * Created by erenalpaslan on 11.09.2023 */ interface Remover { - fun clearBackground(image: T): T? - fun getMaskedImage(input: T, mask: T): T - -} \ No newline at end of file + fun getMaskedImage( + input: T, + mask: T, + ): T +} diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/ImageUpscaler.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/ImageUpscaler.kt index eb228984..eb19235d 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/ImageUpscaler.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/ImageUpscaler.kt @@ -13,8 +13,9 @@ import java.io.File import java.io.FileOutputStream import java.io.IOException -class ImageUpscaler(context: Context) { - +class ImageUpscaler( + context: Context, +) { private var module: Module companion object { @@ -29,13 +30,13 @@ class ImageUpscaler(context: Context) { } catch (e: IOException) { throw RuntimeException( "Failed to load super resolution model: $MODEL_NAME. Ensure it's in app/src/main/assets/", - e + e, ) } } - fun upscaleImage(inputBitmap: Bitmap): Bitmap? { - return try { + fun upscaleImage(inputBitmap: Bitmap): Bitmap? = + try { val inputTensor = preprocessImage(inputBitmap) val outputTensor = module.forward(IValue.from(inputTensor)).toTensor() postprocessOutput(outputTensor) @@ -43,7 +44,6 @@ class ImageUpscaler(context: Context) { e.printStackTrace() // Consider using a proper logger null } - } private fun preprocessImage(bitmap: Bitmap): Tensor { val resizedBitmap = bitmap.scale(INPUT_SIZE, INPUT_SIZE) @@ -51,7 +51,7 @@ class ImageUpscaler(context: Context) { return TensorImageUtils.bitmapToFloat32Tensor( resizedBitmap, floatArrayOf(0.485f, 0.456f, 0.406f), // Example: ImageNet mean - floatArrayOf(0.229f, 0.224f, 0.225f) // Example: ImageNet std + floatArrayOf(0.229f, 0.224f, 0.225f), // Example: ImageNet std ) } @@ -100,7 +100,10 @@ class ImageUpscaler(context: Context) { } @Throws(IOException::class) - private fun assetFilePath(context: Context, assetName: String): String { + private fun assetFilePath( + context: Context, + assetName: String, + ): String { val assetFile = File(context.filesDir, assetName) if (assetFile.exists() && assetFile.length() > 0) { return assetFile.absolutePath @@ -118,4 +121,4 @@ class ImageUpscaler(context: Context) { } return assetFile.absolutePath } -} \ No newline at end of file +} diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/SuperRes.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/SuperRes.kt index 1926292d..19280058 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/SuperRes.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/superres/SuperRes.kt @@ -13,8 +13,9 @@ import java.io.File import java.io.FileOutputStream import java.io.IOException -class SuperRes(context: Context) { - +class SuperRes( + context: Context, +) { private var module: Module companion object { @@ -31,8 +32,8 @@ class SuperRes(context: Context) { } } - fun upscaleImage(inputBitmap: Bitmap): Bitmap? { - return try { + fun upscaleImage(inputBitmap: Bitmap): Bitmap? = + try { // Preprocess the input image val inputTensor = preprocessImage(inputBitmap) @@ -45,7 +46,6 @@ class SuperRes(context: Context) { e.printStackTrace() null } - } private fun preprocessImage(bitmap: Bitmap): Tensor { // Resize image to model input size @@ -55,11 +55,14 @@ class SuperRes(context: Context) { return TensorImageUtils.bitmapToFloat32Tensor( resizedBitmap, floatArrayOf(0.485f, 0.456f, 0.406f), // ImageNet mean - floatArrayOf(0.229f, 0.224f, 0.225f) // ImageNet std + floatArrayOf(0.229f, 0.224f, 0.225f), // ImageNet std ) } - private fun postprocessOutput(outputTensor: Tensor, originalBitmap: Bitmap): Bitmap { + private fun postprocessOutput( + outputTensor: Tensor, + originalBitmap: Bitmap, + ): Bitmap { // Get tensor data val outputData = outputTensor.dataAsFloatArray val shape = outputTensor.shape() @@ -88,8 +91,10 @@ class SuperRes(context: Context) { // Assumes CHW planar format: RRR...GGG...BBB... r = (outputData[baseIndex] * 255).coerceIn(0f, 255f).toInt() g = (outputData[height * width + baseIndex] * 255).coerceIn(0f, 255f).toInt() - b = (outputData[2 * height * width + baseIndex] * 255).coerceIn(0f, 255f) - .toInt() + b = + (outputData[2 * height * width + baseIndex] * 255) + .coerceIn(0f, 255f) + .toInt() } else { throw IllegalArgumentException("Unsupported number of output channels: $channels. Expected 1 or 3.") } @@ -102,7 +107,10 @@ class SuperRes(context: Context) { } @Throws(IOException::class) - private fun assetFilePath(context: Context, assetName: String): String { + private fun assetFilePath( + context: Context, + assetName: String, + ): String { val file = File(context.filesDir, assetName) if (file.exists() && file.length() > 0) { return file.absolutePath diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/FileUtils.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/FileUtils.kt index 884a0095..c5e90bd0 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/FileUtils.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/FileUtils.kt @@ -7,7 +7,10 @@ import java.io.IOException object FileUtils { @Throws(IOException::class) - fun assetFilePath(context: Context, assetName: String): String? { + fun assetFilePath( + context: Context, + assetName: String, + ): String? { val file = File(context.filesDir, assetName) if (file.exists() && file.length() > 0) { return file.absolutePath @@ -24,4 +27,4 @@ object FileUtils { return file.absolutePath } } -} \ No newline at end of file +} diff --git a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/NetUtils.kt b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/NetUtils.kt index fac63954..8652288a 100644 --- a/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/NetUtils.kt +++ b/modules/pytorch-motion-ext/src/main/java/com/tejpratapsingh/motionlib/pytorch/utils/NetUtils.kt @@ -5,7 +5,11 @@ import androidx.core.graphics.createBitmap import androidx.core.graphics.set object NetUtils { - fun convertArrayToBitmap(arr: FloatArray, width: Int, height: Int): Bitmap? { + fun convertArrayToBitmap( + arr: FloatArray, + width: Int, + height: Int, + ): Bitmap? { val grayToneImage = createBitmap(width, height) for (i in 0 until width) { for (j in 0 until height) { @@ -14,4 +18,4 @@ object NetUtils { } return grayToneImage } -} \ No newline at end of file +} diff --git a/modules/pytorch-motion-ext/src/test/java/com/tejpratapsingh/motionlib/pytorch/ExampleUnitTest.kt b/modules/pytorch-motion-ext/src/test/java/com/tejpratapsingh/motionlib/pytorch/ExampleUnitTest.kt index fd13f8a5..a03cb9ef 100644 --- a/modules/pytorch-motion-ext/src/test/java/com/tejpratapsingh/motionlib/pytorch/ExampleUnitTest.kt +++ b/modules/pytorch-motion-ext/src/test/java/com/tejpratapsingh/motionlib/pytorch/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/sdui/src/androidTest/java/com/tejpratapsingh/motion/sdui/ExampleInstrumentedTest.kt b/modules/sdui/src/androidTest/java/com/tejpratapsingh/motion/sdui/ExampleInstrumentedTest.kt index b46b0c19..f7a82249 100644 --- a/modules/sdui/src/androidTest/java/com/tejpratapsingh/motion/sdui/ExampleInstrumentedTest.kt +++ b/modules/sdui/src/androidTest/java/com/tejpratapsingh/motion/sdui/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.tejpratapsingh.motion.sdui -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.tejpratapsingh.motion.sdui.test", appContext.packageName) } -} \ No newline at end of file +} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/data/SduiRenderer.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/data/SduiRenderer.kt index edc78ec0..ffaa1753 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/data/SduiRenderer.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/data/SduiRenderer.kt @@ -15,7 +15,7 @@ import com.tejpratapsingh.motion.sdui.presentation.TextFactory class SduiRenderer( val actionHandler: ActionHandler = DefaultActionHandler(), - val imageLoader: ImageLoader? = null + val imageLoader: ImageLoader? = null, ) { private val gson = Gson() private val factories: MutableMap = mutableMapOf() @@ -26,19 +26,28 @@ class SduiRenderer( register("image", ImageFactory()) } - fun register(type: String, factory: ViewFactory) { + fun register( + type: String, + factory: ViewFactory, + ) { factories[type] = factory } - fun createView(context: Context, json: JsonObject): View { + fun createView( + context: Context, + json: JsonObject, + ): View { val type = json.get("type")?.asString ?: error("Missing type") val factory = factories[type] ?: throw IllegalArgumentException("No factory for type $type") return factory.create(context, json, this) } - fun renderInto(container: ViewGroup, json: String) { + fun renderInto( + container: ViewGroup, + json: String, + ) { val rootJson = gson.fromJson(json, JsonObject::class.java) val view = createView(container.context, rootJson) container.addView(view) } -} \ No newline at end of file +} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ActionHandler.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ActionHandler.kt index 801d33c9..e9069b94 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ActionHandler.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ActionHandler.kt @@ -4,5 +4,8 @@ import android.content.Context import com.google.gson.JsonObject fun interface ActionHandler { - fun handle(context: Context, action: JsonObject) -} \ No newline at end of file + fun handle( + context: Context, + action: JsonObject, + ) +} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ImageLoader.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ImageLoader.kt index c4b72468..b86d4ee8 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ImageLoader.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ImageLoader.kt @@ -3,5 +3,8 @@ package com.tejpratapsingh.motion.sdui.domain import android.widget.ImageView fun interface ImageLoader { - fun load(view: ImageView, url: String) -} \ No newline at end of file + fun load( + view: ImageView, + url: String, + ) +} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ViewFactory.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ViewFactory.kt index 345fa0dc..33d19d63 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ViewFactory.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/domain/ViewFactory.kt @@ -6,5 +6,9 @@ import com.google.gson.JsonObject import com.tejpratapsingh.motion.sdui.data.SduiRenderer fun interface ViewFactory { - fun create(context: Context, json: JsonObject, renderer: SduiRenderer): View -} \ No newline at end of file + fun create( + context: Context, + json: JsonObject, + renderer: SduiRenderer, + ): View +} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ContainerFactory.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ContainerFactory.kt index 3f5444dd..2c8045ab 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ContainerFactory.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ContainerFactory.kt @@ -9,10 +9,15 @@ import com.tejpratapsingh.motion.sdui.data.SduiRenderer import com.tejpratapsingh.motion.sdui.domain.ViewFactory class ContainerFactory : ViewFactory { - override fun create(context: Context, json: JsonObject, renderer: SduiRenderer): View { - val layout = LinearLayout(context).apply { - orientation = if (json.get("orientation")?.asString == "horizontal") LinearLayout.HORIZONTAL else LinearLayout.VERTICAL - } + override fun create( + context: Context, + json: JsonObject, + renderer: SduiRenderer, + ): View { + val layout = + LinearLayout(context).apply { + orientation = if (json.get("orientation")?.asString == "horizontal") LinearLayout.HORIZONTAL else LinearLayout.VERTICAL + } val children = json.getAsJsonArray("children") ?: JsonArray() for (i in 0 until children.size()) { val childJson = children.get(i).asJsonObject @@ -21,4 +26,4 @@ class ContainerFactory : ViewFactory { } return layout } -} \ No newline at end of file +} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/DefaultActionHandler.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/DefaultActionHandler.kt index 08b3ab22..ecdeb087 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/DefaultActionHandler.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/DefaultActionHandler.kt @@ -6,11 +6,14 @@ import com.google.gson.JsonObject import com.tejpratapsingh.motion.sdui.domain.ActionHandler class DefaultActionHandler : ActionHandler { - override fun handle(context: Context, action: JsonObject) { + override fun handle( + context: Context, + action: JsonObject, + ) { val type = action.get("type")?.asString when (type) { "toast" -> Toast.makeText(context, action.get("message")?.asString ?: "", Toast.LENGTH_SHORT).show() else -> Toast.makeText(context, "Unknown action: $type", Toast.LENGTH_SHORT).show() } } -} \ No newline at end of file +} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ImageFactory.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ImageFactory.kt index 1e85edc7..8d185f1c 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ImageFactory.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/ImageFactory.kt @@ -8,7 +8,11 @@ import com.tejpratapsingh.motion.sdui.data.SduiRenderer import com.tejpratapsingh.motion.sdui.domain.ViewFactory class ImageFactory : ViewFactory { - override fun create(context: Context, json: JsonObject, renderer: SduiRenderer): View { + override fun create( + context: Context, + json: JsonObject, + renderer: SduiRenderer, + ): View { val imageView = ImageView(context) renderer.imageLoader?.load(imageView, json.get("url")?.asString ?: "") return imageView diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/TextFactory.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/TextFactory.kt index 58e7adb6..a603def6 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/TextFactory.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/presentation/TextFactory.kt @@ -8,10 +8,13 @@ import com.tejpratapsingh.motion.sdui.data.SduiRenderer import com.tejpratapsingh.motion.sdui.domain.ViewFactory class TextFactory : ViewFactory { - override fun create(context: Context, json: JsonObject, renderer: SduiRenderer): View { - return TextView(context).apply { + override fun create( + context: Context, + json: JsonObject, + renderer: SduiRenderer, + ): View = + TextView(context).apply { text = json.get("text")?.asString ?: "" textSize = json.get("textSize")?.asFloat ?: 16f } - } } diff --git a/modules/sdui/src/test/java/com/tejpratapsingh/motion/sdui/ExampleUnitTest.kt b/modules/sdui/src/test/java/com/tejpratapsingh/motion/sdui/ExampleUnitTest.kt index be5d5295..7c830f9e 100644 --- a/modules/sdui/src/test/java/com/tejpratapsingh/motion/sdui/ExampleUnitTest.kt +++ b/modules/sdui/src/test/java/com/tejpratapsingh/motion/sdui/ExampleUnitTest.kt @@ -1,8 +1,7 @@ package com.tejpratapsingh.motion.sdui -import org.junit.Test - import org.junit.Assert.* +import org.junit.Test /** * Example local unit test, which will execute on the development machine (host). @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/ExampleUnitTest.kt b/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/ExampleUnitTest.kt index 35e40556..bc6484e5 100644 --- a/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/ExampleUnitTest.kt +++ b/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/ImageUtils.kt b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/ImageUtils.kt index ff0c220c..4d7f5c6b 100644 --- a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/ImageUtils.kt +++ b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/ImageUtils.kt @@ -40,7 +40,10 @@ object ImageUtils { return bitmap } - fun applyMask(original: Bitmap, mask: Array): Bitmap { + fun applyMask( + original: Bitmap, + mask: Array, + ): Bitmap { val width = original.width val height = original.height val scaledMask = floatArrayToGrayscaleBitmap(mask).scale(width, height) diff --git a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/TensorFlowImageProcessor.kt b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/TensorFlowImageProcessor.kt index a0c531ef..4127b4f1 100644 --- a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/TensorFlowImageProcessor.kt +++ b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/TensorFlowImageProcessor.kt @@ -16,9 +16,7 @@ object TensorFlowImageProcessor { */ val superResolutionPlugin: MotionPlugin by lazy { object : MotionPlugin { - override fun apply(input: Bitmap): Bitmap { - return superResolutionProcessor.enhance(input) - } + override fun apply(input: Bitmap): Bitmap = superResolutionProcessor.enhance(input) } } @@ -28,9 +26,7 @@ object TensorFlowImageProcessor { */ val backgroundRemovalPlugin: MotionPlugin by lazy { object : MotionPlugin { - override fun apply(input: Bitmap): Bitmap { - return backgroundRemover.removeBackground(input) - } + override fun apply(input: Bitmap): Bitmap = backgroundRemover.removeBackground(input) } } diff --git a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/CarBgRemover.kt b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/CarBgRemover.kt index a63489e8..6b62c8e7 100644 --- a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/CarBgRemover.kt +++ b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/CarBgRemover.kt @@ -16,11 +16,12 @@ import java.io.FileInputStream import java.nio.MappedByteBuffer import java.nio.channels.FileChannel -class CarBgRemover(context: Context) { - +class CarBgRemover( + context: Context, +) { companion object { private const val MODEL_FILENAME = "DeepLabV3-Plus-MobileNet.tflite" - private const val PERSON_CLASS_INDEX = 15 // PASCAL VOC “person” index + private const val PERSON_CLASS_INDEX = 15 // PASCAL VOC “person” index private const val FOREGROUND_THRESHOLD = 0.5f } @@ -29,7 +30,10 @@ class CarBgRemover(context: Context) { Interpreter(loadModelFile(context, MODEL_FILENAME)) } - private fun loadModelFile(context: Context, modelName: String): MappedByteBuffer { + private fun loadModelFile( + context: Context, + modelName: String, + ): MappedByteBuffer { val fileDescriptor = context.assets.openFd(modelName) val inputStream = FileInputStream(fileDescriptor.fileDescriptor) val fileChannel = inputStream.channel @@ -47,7 +51,8 @@ class CarBgRemover(context: Context) { // Preprocessor: resize + normalize to [-1,1] private val preprocessor: ImageProcessor by lazy { - ImageProcessor.Builder() + ImageProcessor + .Builder() .add(ResizeOp(inputHeight, inputWidth, ResizeOp.ResizeMethod.BILINEAR)) .add(NormalizeOp(127.5f, 127.5f)) .build() @@ -64,7 +69,7 @@ class CarBgRemover(context: Context) { // 2. Prepare output buffer val outputTensor = interpreter.getOutputTensor(0) - val outputShape = outputTensor.shape() // [1, H_out, W_out, C] + val outputShape = outputTensor.shape() // [1, H_out, W_out, C] val outputDataType = outputTensor.dataType() val outputBuffer = TensorBuffer.createFixedSize(outputShape, outputDataType) @@ -72,11 +77,11 @@ class CarBgRemover(context: Context) { interpreter.run(inputBuffer, outputBuffer.buffer.rewind()) // 4. Extract “person” mask channel - val logits = outputBuffer.floatArray // length = H_out * W_out * C + val logits = outputBuffer.floatArray // length = H_out * W_out * C val maskWidth = outputShape[2] val maskHeight = outputShape[1] val mask = FloatArray(maskWidth * maskHeight) - val stride = outputShape[3] // number of classes + val stride = outputShape[3] // number of classes for (i in mask.indices) { mask[i] = logits[i * stride + PERSON_CLASS_INDEX] @@ -90,7 +95,7 @@ class CarBgRemover(context: Context) { source: Bitmap, mask: FloatArray, maskWidth: Int, - maskHeight: Int + maskHeight: Int, ): Bitmap { val w = source.width val h = source.height @@ -102,8 +107,12 @@ class CarBgRemover(context: Context) { val my = y * maskHeight / h val idx = my * maskWidth + mx val pixel = source[x, y] - result[x, y] = if (mask[idx] > FOREGROUND_THRESHOLD) pixel - else Color.TRANSPARENT + result[x, y] = + if (mask[idx] > FOREGROUND_THRESHOLD) { + pixel + } else { + Color.TRANSPARENT + } } } return result @@ -113,4 +122,4 @@ class CarBgRemover(context: Context) { fun close() { interpreter.close() } -} \ No newline at end of file +} diff --git a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TfLiteSegmentationHelper.kt b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TfLiteSegmentationHelper.kt index d08e7842..e1f74fb8 100644 --- a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TfLiteSegmentationHelper.kt +++ b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TfLiteSegmentationHelper.kt @@ -16,7 +16,9 @@ import org.tensorflow.lite.support.image.ImageProcessor import org.tensorflow.lite.support.image.TensorImage import org.tensorflow.lite.support.tensorbuffer.TensorBuffer -class TfLiteSegmentationHelper(context: Context) { +class TfLiteSegmentationHelper( + context: Context, +) { private val modelName = "deeplabv3_257_mv_gpu.tflite" private val inputSize = 257 private val interpreter: Interpreter @@ -26,9 +28,7 @@ class TfLiteSegmentationHelper(context: Context) { interpreter = Interpreter(modelFile) } - fun segmentAndRemoveBackground( - bitmap: Bitmap - ): Bitmap { + fun segmentAndRemoveBackground(bitmap: Bitmap): Bitmap { // Run segmentation on background thread val mask = runDeepLabSegmentation(bitmap) return applyMask(bitmap, mask) @@ -40,16 +40,19 @@ class TfLiteSegmentationHelper(context: Context) { // 2. Prepare input tensor val tensorImage = TensorImage.fromBitmap(resized) - val processor = ImageProcessor.Builder() - .add(NormalizeOp(127.5f, 127.5f)) - .build() + val processor = + ImageProcessor + .Builder() + .add(NormalizeOp(127.5f, 127.5f)) + .build() val input = processor.process(tensorImage) // 3. Prepare output buffer - val outputBuffer = TensorBuffer.createFixedSize( - intArrayOf(1, inputSize, inputSize, 21), - org.tensorflow.lite.DataType.FLOAT32 - ) + val outputBuffer = + TensorBuffer.createFixedSize( + intArrayOf(1, inputSize, inputSize, 21), + org.tensorflow.lite.DataType.FLOAT32, + ) // 4. Run inference interpreter.run(input.buffer, outputBuffer.buffer.rewind()) @@ -60,14 +63,21 @@ class TfLiteSegmentationHelper(context: Context) { for (i in maskPixels.indices) { val offset = i * 21 val maxIndex = - scores.copyOfRange(offset, offset + 21).withIndex().maxByOrNull { it.value }!!.index + scores + .copyOfRange(offset, offset + 21) + .withIndex() + .maxByOrNull { it.value }!! + .index maskPixels[i] = if (maxIndex == 15) Color.WHITE else Color.TRANSPARENT } val mask = Bitmap.createBitmap(maskPixels, inputSize, inputSize, Bitmap.Config.ARGB_8888) return mask.scale(originalBitmap.width, originalBitmap.height) } - private fun applyMask(original: Bitmap, mask: Bitmap): Bitmap { + private fun applyMask( + original: Bitmap, + mask: Bitmap, + ): Bitmap { val result = createBitmap(original.width, original.height) val canvas = Canvas(result) val paint = Paint(Paint.ANTI_ALIAS_FLAG) @@ -77,4 +87,4 @@ class TfLiteSegmentationHelper(context: Context) { paint.xfermode = null return result } -} \ No newline at end of file +} diff --git a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TiledBackgroundRemover.kt b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TiledBackgroundRemover.kt index da403d29..a3b7e506 100644 --- a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TiledBackgroundRemover.kt +++ b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/removebg/TiledBackgroundRemover.kt @@ -19,7 +19,7 @@ class TiledBackgroundRemover( context: Context, modelPath: String, private val tileSize: Int = 257, - private val overlap: Int = 64 + private val overlap: Int = 64, ) { private val interpreter: Interpreter @@ -110,7 +110,10 @@ class TiledBackgroundRemover( return ImageUtils.intArrayToGrayscaleBitmap(mask) } - private fun applyMaskToBitmap(src: Bitmap, mask: Bitmap): Bitmap { + private fun applyMaskToBitmap( + src: Bitmap, + mask: Bitmap, + ): Bitmap { val w = src.width val h = src.height val result = createBitmap(w, h) diff --git a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/superres/SuperResolutionProcessor.kt b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/superres/SuperResolutionProcessor.kt index a9bee014..1d9ae4d0 100644 --- a/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/superres/SuperResolutionProcessor.kt +++ b/modules/tensorflow-motion-ext/src/main/java/com/tejpratapsingh/motionlib/tensorflow/superres/SuperResolutionProcessor.kt @@ -8,7 +8,7 @@ import org.tensorflow.lite.support.common.FileUtil class SuperResolutionProcessor( context: Context, - modelFile: String + modelFile: String, ) { private val interpreter: Interpreter @@ -29,13 +29,14 @@ class SuperResolutionProcessor( val outputHeight = inputHeight * 2 // Create output buffer shape: [1, height*2, width*2, 3] - val outputBuffer = Array(1) { - Array(outputHeight) { - Array(outputWidth) { - FloatArray(3) + val outputBuffer = + Array(1) { + Array(outputHeight) { + Array(outputWidth) { + FloatArray(3) + } } } - } // Resize input tensor before running, if model allows dynamic input interpreter.resizeInput(0, intArrayOf(1, inputHeight, inputWidth, 3)) @@ -47,4 +48,4 @@ class SuperResolutionProcessor( // Convert output float array to Bitmap return ImageUtils.floatArrayToBitmap(outputBuffer[0]) } -} \ No newline at end of file +} diff --git a/modules/tensorflow-motion-ext/src/test/java/com/tejpratapsingh/motionlib/tensorflow/ExampleUnitTest.kt b/modules/tensorflow-motion-ext/src/test/java/com/tejpratapsingh/motionlib/tensorflow/ExampleUnitTest.kt index e36e2fd2..3156ed84 100644 --- a/modules/tensorflow-motion-ext/src/test/java/com/tejpratapsingh/motionlib/tensorflow/ExampleUnitTest.kt +++ b/modules/tensorflow-motion-ext/src/test/java/com/tejpratapsingh/motionlib/tensorflow/ExampleUnitTest.kt @@ -13,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +}