diff --git a/build.gradle.kts b/build.gradle.kts index 903df8c37..b5ec09fd4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,3 @@ -import dev.iurysouza.modulegraph.Theme - /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) @@ -17,10 +15,36 @@ import dev.iurysouza.modulegraph.Theme * along with this program. If not, see . */ +/** Added if needed to regenerate module graph + +import dev.iurysouza.modulegraph.Theme + plugins { alias(libs.plugins.dev.iurysouza.modulegraph) apply true } +moduleGraphConfig { +readmePath.set("./ARCHITECTURE.md") +heading = "# 📐 Modules Graph" +theme.set( +Theme.BASE( +mapOf( +"primaryColor" to "#00381a", +"primaryTextColor" to "#d4fcb1", +"primaryBorderColor" to "#14b800", +"lineColor" to "#15c400", +"secondaryColor" to "#283b26", +"tertiaryColor" to "#355238", +"nodeTextColor" to "#e0ffd6", +"edgeLabelBackground" to "#1a1a1a", +"fontSize" to "28px" +) +) +) +} + + **/ + buildscript { repositories { gradlePluginPortal() @@ -46,24 +70,4 @@ buildscript { tasks.register("clean", Delete::class) { delete(rootProject.layout.buildDirectory) -} - -moduleGraphConfig { - readmePath.set("./ARCHITECTURE.md") - heading = "# 📐 Modules Graph" - theme.set( - Theme.BASE( - mapOf( - "primaryColor" to "#00381a", - "primaryTextColor" to "#d4fcb1", - "primaryBorderColor" to "#14b800", - "lineColor" to "#15c400", - "secondaryColor" to "#283b26", - "tertiaryColor" to "#355238", - "nodeTextColor" to "#e0ffd6", - "edgeLabelBackground" to "#1a1a1a", - "fontSize" to "28px" - ) - ) - ) } \ No newline at end of file diff --git a/core/resources/src/main/res/values/strings.xml b/core/resources/src/main/res/values/strings.xml index 1b825a243..d3f6aee92 100644 --- a/core/resources/src/main/res/values/strings.xml +++ b/core/resources/src/main/res/values/strings.xml @@ -1602,4 +1602,6 @@ Scan any Barcode (QR, EAN, AZTEC, …) and get it\'s content or paste your text to generate new one No Barcode Found Generated Barcode Will Be Here + Audio Cover Extractor + Extract album cover images from audio files, most common formats are supported \ No newline at end of file diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt index ecc88133d..a9f48e12f 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt @@ -199,7 +199,7 @@ object ContextUtils { onShowToast: (message: String, icon: ImageVector) -> Unit, onNavigate: (Screen) -> Unit, onGetUris: (List) -> Unit, - onHasExtraImageType: (String) -> Unit, + onHasExtraImageType: (String) -> Unit, //TODO: Add normal sealed class instead of string isHasUris: Boolean, onWantGithubReview: () -> Unit, isOpenEditInsteadOfPreview: Boolean, @@ -294,10 +294,17 @@ object ContextUtils { onHasExtraImageType(text) onGetUris(listOf()) } else { + val isAudio = intent.type?.startsWith("audio/") == true + when (intent.action) { Intent.ACTION_SEND_MULTIPLE -> { intent.parcelableArrayList(Intent.EXTRA_STREAM)?.let { - onNavigate(Screen.Zip(it)) + if (isAudio) { + onHasExtraImageType("audio") + onGetUris(it) + } else { + onNavigate(Screen.Zip(it)) + } } } @@ -307,7 +314,12 @@ object ContextUtils { onHasExtraImageType("$BackupFileExtension $it") return } - onHasExtraImageType("file") + if (isAudio) { + onHasExtraImageType("audio") + } else { + onHasExtraImageType("file") + } + onGetUris(listOf(it)) } } @@ -326,10 +338,20 @@ object ContextUtils { return } - onHasExtraImageType("file") + if (isAudio) { + onHasExtraImageType("audio") + } else { + onHasExtraImageType("file") + } + onGetUris(uris) } else if (uris.isNotEmpty()) { - onNavigate(Screen.Zip(uris)) + if (isAudio) { + onHasExtraImageType("audio") + onGetUris(uris) + } else { + onNavigate(Screen.Zip(uris)) + } } else { Unit } diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt index 67524ecaf..bd6b68ed0 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt @@ -726,6 +726,15 @@ sealed class Screen( subtitle = R.string.image_cutting_sub ) + @Serializable + data class AudioCoverExtractor( + val uris: List? = null + ) : Screen( + id = 39, + title = R.string.audio_cover_extractor, + subtitle = R.string.audio_cover_extractor_sub + ) + companion object : ScreenConstants by ScreenConstantsImpl } diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/ScreenUtils.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/ScreenUtils.kt index 7fc9b3527..0459b4aa6 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/ScreenUtils.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/ScreenUtils.kt @@ -34,6 +34,7 @@ import androidx.compose.material.icons.outlined.Grain import androidx.compose.material.icons.outlined.Photo import androidx.compose.material.icons.outlined.PictureAsPdf import androidx.compose.material.icons.outlined.QrCode +import androidx.compose.material.icons.rounded.Album import androidx.compose.material.icons.rounded.Compare import androidx.compose.material.icons.rounded.ContentCut import androidx.compose.material.icons.rounded.Tag @@ -71,7 +72,51 @@ import ru.tech.imageresizershrinker.core.resources.icons.Stack import ru.tech.imageresizershrinker.core.resources.icons.Toolbox import ru.tech.imageresizershrinker.core.resources.icons.VectorPolyline import ru.tech.imageresizershrinker.core.resources.icons.WebpBox -import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.* +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ApngTools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.AudioCoverExtractor +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Base64Tools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ChecksumTools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Cipher +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.CollageMaker +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ColorTools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Compare +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Crop +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.DeleteExif +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.DocumentScanner +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Draw +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.EasterEgg +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.EditExif +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.EraseBackground +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Filter +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.FormatConversion +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.GeneratePalette +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.GifTools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.GradientMaker +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ImageCutter +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ImagePreview +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ImageSplitting +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ImageStacking +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ImageStitching +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.JxlTools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.LibrariesInfo +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.LimitResize +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.LoadNetImage +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Main +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.MarkupLayers +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.MeshGradients +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.NoiseGeneration +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.PdfTools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.PickColorFromImage +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.RecognizeText +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ResizeAndConvert +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.ScanQrCode +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Settings +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.SingleEdit +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.SvgMaker +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Watermarking +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.WebpTools +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.WeightResize +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen.Zip import android.net.Uri as AndroidUri internal fun Screen.isBetaFeature(): Boolean = when (this) { @@ -82,7 +127,7 @@ internal fun Screen.isBetaFeature(): Boolean = when (this) { internal fun Screen.simpleName(): String? = when (this) { is ApngTools -> "APNG_Tools" is Cipher -> "Cipher" - is Screen.Compare -> "Compare" + is Compare -> "Compare" is Crop -> "Crop" is DeleteExif -> "Delete_Exif" is Draw -> "Draw" @@ -109,7 +154,7 @@ internal fun Screen.simpleName(): String? = when (this) { is Zip -> "Zip" is SvgMaker -> "Svg" is FormatConversion -> "Convert" - is Screen.DocumentScanner -> "Document_Scanner" + is DocumentScanner -> "Document_Scanner" is ScanQrCode -> "QR_Code" is ImageStacking -> "Image_Stacking" is ImageSplitting -> "Image_Splitting" @@ -124,6 +169,7 @@ internal fun Screen.simpleName(): String? = when (this) { is MeshGradients -> "Mesh_Gradients" is EditExif -> "Edit_EXIF" is ImageCutter -> "Image_Cutting" + is AudioCoverExtractor -> "Audio_Cover_Extractor" } internal fun Screen.icon(): ImageVector? = when (this) { @@ -136,7 +182,7 @@ internal fun Screen.icon(): ImageVector? = when (this) { is SingleEdit -> Icons.Outlined.ImageEdit is ApngTools -> Icons.Rounded.ApngBox is Cipher -> Icons.Outlined.Encrypted - is Screen.Compare -> Icons.Rounded.Compare + is Compare -> Icons.Rounded.Compare is Crop -> Icons.Rounded.CropSmall is DeleteExif -> Icons.Outlined.Exif is Draw -> Icons.Outlined.Draw @@ -159,7 +205,7 @@ internal fun Screen.icon(): ImageVector? = when (this) { is Zip -> Icons.Outlined.FolderZip is SvgMaker -> Icons.Outlined.VectorPolyline is FormatConversion -> Icons.Outlined.ImageConvert - is Screen.DocumentScanner -> Icons.Outlined.DocumentScanner + is DocumentScanner -> Icons.Outlined.DocumentScanner is ScanQrCode -> Icons.Outlined.QrCode is ImageStacking -> Icons.Outlined.ImageOverlay is ImageSplitting -> Icons.Outlined.SplitAlt @@ -172,6 +218,7 @@ internal fun Screen.icon(): ImageVector? = when (this) { is ChecksumTools -> Icons.Rounded.Tag is EditExif -> Icons.Outlined.ExifEdit is ImageCutter -> Icons.Rounded.ContentCut + is AudioCoverExtractor -> Icons.Rounded.Album } internal object UriSerializer : KSerializer { @@ -261,7 +308,8 @@ internal object ScreenConstantsImpl : ScreenConstants { Zip(), JxlTools(), ApngTools(), - WebpTools() + WebpTools(), + AudioCoverExtractor() ), title = R.string.tools, selectedIcon = Icons.Rounded.Toolbox, @@ -274,5 +322,5 @@ internal object ScreenConstantsImpl : ScreenConstants { typedEntries.flatMap { it.entries }.sortedBy { it.id } } - override val FEATURES_COUNT = 66 + override val FEATURES_COUNT = 67 } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt index 549ff35ea..0a569adda 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt @@ -35,7 +35,7 @@ import java.util.Locale @Composable internal fun List.screenList( - extraImageType: String? + extraImageType: String? //TODO: Add normal sealed class instead of string ): State> { val uris = this val context = LocalContext.current @@ -59,6 +59,17 @@ internal fun List.screenList( ) } } + val audioAvailableScreens by remember(uris) { + derivedStateOf { + listOf( + Screen.AudioCoverExtractor(uris) + ) + if (uris.size > 1) { + filesAvailableScreens + } else { + listOf(Screen.Zip(uris)) + } + } + } val gifAvailableScreens by remember(uris) { derivedStateOf { listOf( @@ -272,6 +283,7 @@ internal fun List.screenList( ) { derivedStateOf { when { + extraImageType == "audio" -> audioAvailableScreens extraImageType == "pdf" -> pdfAvailableScreens extraImageType == "gif" -> gifAvailableScreens extraImageType == "file" -> filesAvailableScreens diff --git a/feature/audio-cover-extractor/.gitignore b/feature/audio-cover-extractor/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/feature/audio-cover-extractor/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/audio-cover-extractor/build.gradle.kts b/feature/audio-cover-extractor/build.gradle.kts new file mode 100644 index 000000000..ecfd60698 --- /dev/null +++ b/feature/audio-cover-extractor/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +plugins { + alias(libs.plugins.image.toolbox.library) + alias(libs.plugins.image.toolbox.feature) + alias(libs.plugins.image.toolbox.hilt) + alias(libs.plugins.image.toolbox.compose) +} + +android.namespace = "ru.tech.imageresizershrinker.feature.audio_cover_extractor" + +dependencies { + implementation(libs.ffmpeg.metadata.retriever.core) + implementation(libs.ffmpeg.metadata.retriever.native) +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/AndroidManifest.xml b/feature/audio-cover-extractor/src/main/AndroidManifest.xml new file mode 100644 index 000000000..44008a433 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/data/AndroidAudioCoverRetriever.kt b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/data/AndroidAudioCoverRetriever.kt new file mode 100644 index 000000000..e92f125e0 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/data/AndroidAudioCoverRetriever.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.audio_cover_extractor.data + +import android.content.Context +import android.graphics.Bitmap +import androidx.core.net.toUri +import androidx.exifinterface.media.ExifInterface +import dagger.hilt.android.qualifiers.ApplicationContext +import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder +import ru.tech.imageresizershrinker.core.domain.image.ImageCompressor +import ru.tech.imageresizershrinker.core.domain.image.ImageGetter +import ru.tech.imageresizershrinker.core.domain.image.ShareProvider +import ru.tech.imageresizershrinker.core.domain.image.model.ImageFormat +import ru.tech.imageresizershrinker.core.domain.image.model.Quality +import ru.tech.imageresizershrinker.core.domain.resource.ResourceManager +import ru.tech.imageresizershrinker.core.resources.R +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.domain.AudioCoverRetriever +import wseemann.media.FFmpegMediaMetadataRetriever +import javax.inject.Inject + +internal class AndroidAudioCoverRetriever @Inject constructor( + @ApplicationContext private val context: Context, + private val imageCompressor: ImageCompressor, + private val shareProvider: ShareProvider, + private val imageGetter: ImageGetter, + dispatchersHolder: DispatchersHolder, + resourceManager: ResourceManager +) : AudioCoverRetriever, + DispatchersHolder by dispatchersHolder, + ResourceManager by resourceManager { + + override suspend fun loadCover( + audioUri: String + ): Result { + val pictureData = FFmpegMediaMetadataRetriever().apply { + setDataSource( + context, + audioUri.toUri() + ) + }.embeddedPicture + + return imageGetter.getImage( + data = pictureData, + originalSize = true + )?.let { bitmap -> + shareProvider.cacheData( + writeData = { + it.writeBytes( + imageCompressor.compress( + image = bitmap, + imageFormat = ImageFormat.Png.Lossless, + quality = Quality.Base() + ) + ) + }, + filename = "${audioUri.substringBeforeLast('.')}.png" + )?.let(Result.Companion::success) + } ?: Result.failure(NullPointerException(getString(R.string.no_image))) + } + + override suspend fun loadCover( + audioData: ByteArray + ): Result { + return loadCover( + shareProvider.cacheData( + writeData = { + it.writeBytes(audioData) + }, + filename = "Audio_data_${System.currentTimeMillis()}.mp3" + ) + ?: return Result.failure(NullPointerException(getString(R.string.filename_is_not_set))) + ) + } + + +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/di/AudioCoverExtractorModule.kt b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/di/AudioCoverExtractorModule.kt new file mode 100644 index 000000000..4aa62a5eb --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/di/AudioCoverExtractorModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.audio_cover_extractor.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.data.AndroidAudioCoverRetriever +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.domain.AudioCoverRetriever +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface AudioCoverExtractorModule { + + @Binds + @Singleton + fun extractor( + impl: AndroidAudioCoverRetriever + ): AudioCoverRetriever + +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/domain/AudioCoverRetriever.kt b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/domain/AudioCoverRetriever.kt new file mode 100644 index 000000000..9a8185f68 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/domain/AudioCoverRetriever.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.audio_cover_extractor.domain + +interface AudioCoverRetriever { + + suspend fun loadCover( + audioUri: String + ): Result + + suspend fun loadCover( + audioData: ByteArray + ): Result + +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/AudioCoverExtractorContent.kt b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/AudioCoverExtractorContent.kt new file mode 100644 index 000000000..c8f684783 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/AudioCoverExtractorContent.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui + +import androidx.compose.runtime.Composable +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui.screenLogic.AudioCoverExtractorComponent + +@Composable +fun AudioCoverExtractorContent( + component: AudioCoverExtractorComponent +) { + +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/components/AudioWithCover.kt b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/components/AudioWithCover.kt new file mode 100644 index 000000000..02e7b6faa --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/components/AudioWithCover.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui.components + +import android.net.Uri + +data class AudioWithCover( + val audioUri: Uri, + val imageCoverUri: Uri?, + val isLoading: Boolean +) \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/screenLogic/AudioCoverExtractorComponent.kt b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/screenLogic/AudioCoverExtractorComponent.kt new file mode 100644 index 000000000..b7af1337b --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/ru/tech/imageresizershrinker/feature/audio_cover_extractor/ui/screenLogic/AudioCoverExtractorComponent.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui.screenLogic + +import android.net.Uri +import androidx.core.net.toUri +import com.arkivanov.decompose.ComponentContext +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder +import ru.tech.imageresizershrinker.core.ui.utils.BaseComponent +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.domain.AudioCoverRetriever +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui.components.AudioWithCover + +class AudioCoverExtractorComponent @AssistedInject constructor( + @Assisted componentContext: ComponentContext, + @Assisted val initialUris: List?, + @Assisted val onGoBack: () -> Unit, + @Assisted val onNavigate: (Screen) -> Unit, + private val audioCoverRetriever: AudioCoverRetriever, + dispatchersHolder: DispatchersHolder +) : BaseComponent(dispatchersHolder, componentContext) { + + init { + debounce { + initialUris?.let(::updateCovers) + } + } + + private val _covers: MutableStateFlow> = MutableStateFlow(emptyList()) + val covers: StateFlow> = _covers.asStateFlow() + + + fun updateCovers(uris: List) { + val audioUris = uris.distinct() + + componentScope.launch { + _covers.update { + audioUris.map { + AudioWithCover( + audioUri = it, + imageCoverUri = null, + isLoading = true + ) + } + } + + val newCovers = audioUris.map { audioUri -> + async { + val coverUri = audioCoverRetriever.loadCover(audioUri.toString()).getOrNull() + + val newCover = AudioWithCover( + audioUri = audioUri, + imageCoverUri = coverUri?.toUri(), + isLoading = false + ) + + _covers.update { covers -> + covers.toMutableList().apply { + val index = indexOfFirst { it.audioUri == audioUri }.takeIf { it >= 0 } + ?: return@update covers + + set(index, newCover) + } + } + + newCover + } + } + + _covers.update { + newCovers.awaitAll().filter { it.imageCoverUri != null } + } + } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): AudioCoverExtractorComponent + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/data/AndroidPdfManager.kt b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/data/AndroidPdfManager.kt index 1fd66d418..534fee711 100644 --- a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/data/AndroidPdfManager.kt +++ b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/data/AndroidPdfManager.kt @@ -31,9 +31,7 @@ import coil3.request.ImageRequest import coil3.size.Size import coil3.toBitmap import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import ru.tech.imageresizershrinker.core.data.utils.aspectRatio import ru.tech.imageresizershrinker.core.data.utils.getSuitableConfig @@ -108,17 +106,16 @@ internal class AndroidPdfManager @Inject constructor( } } - override fun convertPdfToImages( + override suspend fun convertPdfToImages( pdfUri: String, pages: List?, preset: Preset.Percentage, onGetPagesCount: suspend (Int) -> Unit, onProgressChange: suspend (Int, Bitmap) -> Unit, onComplete: suspend () -> Unit - ) = CoroutineScope(ioDispatcher).launch { + ): Unit = withContext(ioDispatcher) { context.contentResolver.openFileDescriptor( - pdfUri.toUri(), - "r" + pdfUri.toUri(), "r" )?.use { fileDescriptor -> withContext(defaultDispatcher) { val pdfRenderer = PdfRenderer(fileDescriptor) diff --git a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/domain/PdfManager.kt b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/domain/PdfManager.kt index 641a1f130..a2aff8ca2 100644 --- a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/domain/PdfManager.kt +++ b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/domain/PdfManager.kt @@ -17,7 +17,6 @@ package ru.tech.imageresizershrinker.feature.pdf_tools.domain -import kotlinx.coroutines.Job import ru.tech.imageresizershrinker.core.domain.image.model.Preset import ru.tech.imageresizershrinker.core.domain.model.IntegerSize @@ -34,13 +33,13 @@ interface PdfManager { preset: Preset.Percentage ): ByteArray - fun convertPdfToImages( + suspend fun convertPdfToImages( pdfUri: String, pages: List?, preset: Preset.Percentage, onGetPagesCount: suspend (Int) -> Unit, onProgressChange: suspend (Int, I) -> Unit, onComplete: suspend () -> Unit = {} - ): Job + ) } \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/screenLogic/PdfToolsComponent.kt b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/screenLogic/PdfToolsComponent.kt index f3ca302e2..18e8cf718 100644 --- a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/screenLogic/PdfToolsComponent.kt +++ b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/screenLogic/PdfToolsComponent.kt @@ -215,44 +215,46 @@ class PdfToolsComponent @AssistedInject internal constructor( _done.value = 0 _left.value = 1 val results = mutableListOf() - savingJob = pdfManager.convertPdfToImages( - pdfUri = _pdfToImageState.value?.uri.toString(), - pages = _pdfToImageState.value?.selectedPages, - preset = presetSelected, - onProgressChange = { _, bitmap -> - val imageInfo = imageTransformer.applyPresetBy( - image = bitmap, - preset = _presetSelected.value, - currentInfo = imageInfo - ) - - results.add( - fileController.save( - saveTarget = ImageSaveTarget( - imageInfo = imageInfo, - metadata = null, - originalUri = _pdfToImageState.value?.uri.toString(), - sequenceNumber = _done.value + 1, - data = imageCompressor.compressAndTransform( - image = bitmap, - imageInfo = imageInfo - ) - ), - keepOriginalMetadata = false, - oneTimeSaveLocationUri = oneTimeSaveLocationUri + savingJob = componentScope.launch { + pdfManager.convertPdfToImages( + pdfUri = _pdfToImageState.value?.uri.toString(), + pages = _pdfToImageState.value?.selectedPages, + preset = presetSelected, + onProgressChange = { _, bitmap -> + val imageInfo = imageTransformer.applyPresetBy( + image = bitmap, + preset = _presetSelected.value, + currentInfo = imageInfo ) - ) - _done.value += 1 - }, - onGetPagesCount = { size -> - _left.update { size } - _isSaving.value = true - }, - onComplete = { - _isSaving.value = false - onComplete(results.onSuccess(::registerSave)) - } - ) + + results.add( + fileController.save( + saveTarget = ImageSaveTarget( + imageInfo = imageInfo, + metadata = null, + originalUri = _pdfToImageState.value?.uri.toString(), + sequenceNumber = _done.value + 1, + data = imageCompressor.compressAndTransform( + image = bitmap, + imageInfo = imageInfo + ) + ), + keepOriginalMetadata = false, + oneTimeSaveLocationUri = oneTimeSaveLocationUri + ) + ) + _done.value += 1 + }, + onGetPagesCount = { size -> + _left.update { size } + _isSaving.value = true + }, + onComplete = { + _isSaving.value = false + onComplete(results.onSuccess(::registerSave)) + } + ) + } } fun convertImagesToPdf(onComplete: () -> Unit) { @@ -315,39 +317,41 @@ class PdfToolsComponent @AssistedInject internal constructor( _left.value = 1 _isSaving.value = false val uris: MutableList = mutableListOf() - savingJob = pdfManager.convertPdfToImages( - pdfUri = _pdfToImageState.value?.uri.toString(), - pages = _pdfToImageState.value?.selectedPages, - onProgressChange = { _, bitmap -> - imageInfo.copy( - originalUri = _pdfToImageState.value?.uri?.toString() - ).let { - imageTransformer.applyPresetBy( - image = bitmap, - preset = _presetSelected.value, - currentInfo = it - ) - }.apply { - uris.add( - shareProvider.cacheImage( - imageInfo = this, - image = bitmap + savingJob = componentScope.launch { + pdfManager.convertPdfToImages( + pdfUri = _pdfToImageState.value?.uri.toString(), + pages = _pdfToImageState.value?.selectedPages, + onProgressChange = { _, bitmap -> + imageInfo.copy( + originalUri = _pdfToImageState.value?.uri?.toString() + ).let { + imageTransformer.applyPresetBy( + image = bitmap, + preset = _presetSelected.value, + currentInfo = it ) - ) + }.apply { + uris.add( + shareProvider.cacheImage( + imageInfo = this, + image = bitmap + ) + ) + } + _done.value += 1 + }, + preset = presetSelected, + onGetPagesCount = { size -> + _left.update { size } + _isSaving.value = true + }, + onComplete = { + _isSaving.value = false + shareProvider.shareUris(uris.filterNotNull()) + onComplete() } - _done.value += 1 - }, - preset = presetSelected, - onGetPagesCount = { size -> - _left.update { size } - _isSaving.value = true - }, - onComplete = { - _isSaving.value = false - shareProvider.shareUris(uris.filterNotNull()) - onComplete() - } - ) + ) + } } is Screen.PdfTools.Type.Preview -> { diff --git a/feature/root/build.gradle.kts b/feature/root/build.gradle.kts index 0bc82908b..fcff70dd0 100644 --- a/feature/root/build.gradle.kts +++ b/feature/root/build.gradle.kts @@ -69,4 +69,5 @@ dependencies { implementation(projects.feature.meshGradients) implementation(projects.feature.editExif) implementation(projects.feature.imageCutting) + implementation(projects.feature.audioCoverExtractor) } \ No newline at end of file diff --git a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt index 4c62883cd..b1f2ad7fd 100644 --- a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt +++ b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt @@ -22,6 +22,7 @@ import ru.tech.imageresizershrinker.collage_maker.presentation.screenLogic.Colla import ru.tech.imageresizershrinker.color_tools.presentation.screenLogic.ColorToolsComponent import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen import ru.tech.imageresizershrinker.feature.apng_tools.presentation.screenLogic.ApngToolsComponent +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui.screenLogic.AudioCoverExtractorComponent import ru.tech.imageresizershrinker.feature.base64_tools.presentation.screenLogic.Base64ToolsComponent import ru.tech.imageresizershrinker.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent import ru.tech.imageresizershrinker.feature.cipher.presentation.screenLogic.CipherComponent @@ -53,6 +54,7 @@ import ru.tech.imageresizershrinker.feature.pick_color.presentation.screenLogic. import ru.tech.imageresizershrinker.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent import ru.tech.imageresizershrinker.feature.resize_convert.presentation.screenLogic.ResizeAndConvertComponent import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ApngTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.AudioCoverExtractor import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Base64Tools import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ChecksumTools import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Cipher @@ -154,7 +156,8 @@ internal class ChildProvider @Inject constructor( private val checksumToolsComponentFactory: ChecksumToolsComponent.Factory, private val meshGradientsComponentFactory: MeshGradientsComponent.Factory, private val editExifComponentFactory: EditExifComponent.Factory, - private val imageCutterComponentFactory: ImageCutterComponent.Factory + private val imageCutterComponentFactory: ImageCutterComponent.Factory, + private val audioCoverExtractorComponentFactory: AudioCoverExtractorComponent.Factory ) { fun RootComponent.createChild( config: Screen, @@ -541,5 +544,14 @@ internal class ChildProvider @Inject constructor( onNavigate = ::navigateTo ) ) + + is Screen.AudioCoverExtractor -> AudioCoverExtractor( + audioCoverExtractorComponentFactory( + componentContext = componentContext, + initialUris = config.uris, + onGoBack = ::navigateBack, + onNavigate = ::navigateTo + ) + ) } } \ No newline at end of file diff --git a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/NavigationChild.kt b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/NavigationChild.kt index 93b22e59f..b11a022a8 100644 --- a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/NavigationChild.kt +++ b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/NavigationChild.kt @@ -24,6 +24,8 @@ import ru.tech.imageresizershrinker.color_tools.presentation.ColorToolsContent import ru.tech.imageresizershrinker.color_tools.presentation.screenLogic.ColorToolsComponent import ru.tech.imageresizershrinker.feature.apng_tools.presentation.ApngToolsContent import ru.tech.imageresizershrinker.feature.apng_tools.presentation.screenLogic.ApngToolsComponent +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui.AudioCoverExtractorContent +import ru.tech.imageresizershrinker.feature.audio_cover_extractor.ui.screenLogic.AudioCoverExtractorComponent import ru.tech.imageresizershrinker.feature.base64_tools.presentation.Base64ToolsContent import ru.tech.imageresizershrinker.feature.base64_tools.presentation.screenLogic.Base64ToolsComponent import ru.tech.imageresizershrinker.feature.checksum_tools.presentation.ChecksumToolsContent @@ -108,230 +110,237 @@ import ru.tech.imageresizershrinker.noise_generation.presentation.NoiseGeneratio import ru.tech.imageresizershrinker.noise_generation.presentation.screenLogic.NoiseGenerationComponent -internal sealed class NavigationChild { +internal sealed interface NavigationChild { @Composable - abstract fun Content() + fun Content() - class ApngTools(val component: ApngToolsComponent) : NavigationChild() { + class ApngTools(private val component: ApngToolsComponent) : NavigationChild { @Composable override fun Content() = ApngToolsContent(component) } - class Cipher(val component: CipherComponent) : NavigationChild() { + class Cipher(private val component: CipherComponent) : NavigationChild { @Composable override fun Content() = CipherContent(component) } - class CollageMaker(val component: CollageMakerComponent) : NavigationChild() { + class CollageMaker(private val component: CollageMakerComponent) : NavigationChild { @Composable override fun Content() = CollageMakerContent(component) } - class ColorTools(val component: ColorToolsComponent) : NavigationChild() { + class ColorTools(private val component: ColorToolsComponent) : NavigationChild { @Composable override fun Content() = ColorToolsContent(component) } - class Compare(val component: CompareComponent) : NavigationChild() { + class Compare(private val component: CompareComponent) : NavigationChild { @Composable override fun Content() = CompareContent(component) } - class Crop(val component: CropComponent) : NavigationChild() { + class Crop(private val component: CropComponent) : NavigationChild { @Composable override fun Content() = CropContent(component) } - class DeleteExif(val component: DeleteExifComponent) : NavigationChild() { + class DeleteExif(private val component: DeleteExifComponent) : NavigationChild { @Composable override fun Content() = DeleteExifContent(component) } - class DocumentScanner(val component: DocumentScannerComponent) : NavigationChild() { + class DocumentScanner(private val component: DocumentScannerComponent) : NavigationChild { @Composable override fun Content() = DocumentScannerContent(component) } - class Draw(val component: DrawComponent) : NavigationChild() { + class Draw(private val component: DrawComponent) : NavigationChild { @Composable override fun Content() = DrawContent(component) } - class EasterEgg(val component: EasterEggComponent) : NavigationChild() { + class EasterEgg(private val component: EasterEggComponent) : NavigationChild { @Composable override fun Content() = EasterEggContent(component) } - class EraseBackground(val component: EraseBackgroundComponent) : NavigationChild() { + class EraseBackground(private val component: EraseBackgroundComponent) : NavigationChild { @Composable override fun Content() = EraseBackgroundContent(component) } - class Filter(val component: FiltersComponent) : NavigationChild() { + class Filter(private val component: FiltersComponent) : NavigationChild { @Composable override fun Content() = FiltersContent(component) } - class FormatConversion(val component: FormatConversionComponent) : NavigationChild() { + class FormatConversion(private val component: FormatConversionComponent) : NavigationChild { @Composable override fun Content() = FormatConversionContent(component) } - class GeneratePalette(val component: GeneratePaletteComponent) : NavigationChild() { + class GeneratePalette(private val component: GeneratePaletteComponent) : NavigationChild { @Composable override fun Content() = GeneratePaletteContent(component) } - class GifTools(val component: GifToolsComponent) : NavigationChild() { + class GifTools(private val component: GifToolsComponent) : NavigationChild { @Composable override fun Content() = GifToolsContent(component) } - class GradientMaker(val component: GradientMakerComponent) : NavigationChild() { + class GradientMaker(private val component: GradientMakerComponent) : NavigationChild { @Composable override fun Content() = GradientMakerContent(component) } - class ImagePreview(val component: ImagePreviewComponent) : NavigationChild() { + class ImagePreview(private val component: ImagePreviewComponent) : NavigationChild { @Composable override fun Content() = ImagePreviewContent(component) } - class ImageSplitting(val component: ImageSplitterComponent) : NavigationChild() { + class ImageSplitting(private val component: ImageSplitterComponent) : NavigationChild { @Composable override fun Content() = ImageSplitterContent(component) } - class ImageStacking(val component: ImageStackingComponent) : NavigationChild() { + class ImageStacking(private val component: ImageStackingComponent) : NavigationChild { @Composable override fun Content() = ImageStackingContent(component) } - class ImageStitching(val component: ImageStitchingComponent) : NavigationChild() { + class ImageStitching(private val component: ImageStitchingComponent) : NavigationChild { @Composable override fun Content() = ImageStitchingContent(component) } - class JxlTools(val component: JxlToolsComponent) : NavigationChild() { + class JxlTools(private val component: JxlToolsComponent) : NavigationChild { @Composable override fun Content() = JxlToolsContent(component) } - class LimitResize(val component: LimitsResizeComponent) : NavigationChild() { + class LimitResize(private val component: LimitsResizeComponent) : NavigationChild { @Composable override fun Content() = LimitsResizeContent(component) } - class LoadNetImage(val component: LoadNetImageComponent) : NavigationChild() { + class LoadNetImage(private val component: LoadNetImageComponent) : NavigationChild { @Composable override fun Content() = LoadNetImageContent(component) } - class Main(val component: MainComponent) : NavigationChild() { + class Main(private val component: MainComponent) : NavigationChild { @Composable override fun Content() = MainContent(component) } - class NoiseGeneration(val component: NoiseGenerationComponent) : NavigationChild() { + class NoiseGeneration(private val component: NoiseGenerationComponent) : NavigationChild { @Composable override fun Content() = NoiseGenerationContent(component) } - class PdfTools(val component: PdfToolsComponent) : NavigationChild() { + class PdfTools(private val component: PdfToolsComponent) : NavigationChild { @Composable override fun Content() = PdfToolsContent(component) } - class PickColorFromImage(val component: PickColorFromImageComponent) : NavigationChild() { + class PickColorFromImage(private val component: PickColorFromImageComponent) : NavigationChild { @Composable override fun Content() = PickColorFromImageContent(component) } - class RecognizeText(val component: RecognizeTextComponent) : NavigationChild() { + class RecognizeText(private val component: RecognizeTextComponent) : NavigationChild { @Composable override fun Content() = RecognizeTextContent(component) } - class ResizeAndConvert(val component: ResizeAndConvertComponent) : NavigationChild() { + class ResizeAndConvert(private val component: ResizeAndConvertComponent) : NavigationChild { @Composable override fun Content() = ResizeAndConvertContent(component) } - class ScanQrCode(val component: ScanQrCodeComponent) : NavigationChild() { + class ScanQrCode(private val component: ScanQrCodeComponent) : NavigationChild { @Composable override fun Content() = ScanQrCodeContent(component) } - class Settings(val component: SettingsComponent) : NavigationChild() { + class Settings(private val component: SettingsComponent) : NavigationChild { @Composable override fun Content() = SettingsContent(component) } - class SingleEdit(val component: SingleEditComponent) : NavigationChild() { + class SingleEdit(private val component: SingleEditComponent) : NavigationChild { @Composable override fun Content() = SingleEditContent(component) } - class SvgMaker(val component: SvgMakerComponent) : NavigationChild() { + class SvgMaker(private val component: SvgMakerComponent) : NavigationChild { @Composable override fun Content() = SvgMakerContent(component) } - class Watermarking(val component: WatermarkingComponent) : NavigationChild() { + class Watermarking(private val component: WatermarkingComponent) : NavigationChild { @Composable override fun Content() = WatermarkingContent(component) } - class WebpTools(val component: WebpToolsComponent) : NavigationChild() { + class WebpTools(private val component: WebpToolsComponent) : NavigationChild { @Composable override fun Content() = WebpToolsContent(component) } - class WeightResize(val component: WeightResizeComponent) : NavigationChild() { + class WeightResize(private val component: WeightResizeComponent) : NavigationChild { @Composable override fun Content() = WeightResizeContent(component) } - class Zip(val component: ZipComponent) : NavigationChild() { + class Zip(private val component: ZipComponent) : NavigationChild { @Composable override fun Content() = ZipContent(component) } - class LibrariesInfo(val component: LibrariesInfoComponent) : NavigationChild() { + class LibrariesInfo(private val component: LibrariesInfoComponent) : NavigationChild { @Composable override fun Content() = LibrariesInfoContent(component) } - class MarkupLayers(val component: MarkupLayersComponent) : NavigationChild() { + class MarkupLayers(private val component: MarkupLayersComponent) : NavigationChild { @Composable override fun Content() = MarkupLayersContent(component) } - class Base64Tools(val component: Base64ToolsComponent) : NavigationChild() { + class Base64Tools(private val component: Base64ToolsComponent) : NavigationChild { @Composable override fun Content() = Base64ToolsContent(component) } - class ChecksumTools(val component: ChecksumToolsComponent) : NavigationChild() { + class ChecksumTools(private val component: ChecksumToolsComponent) : NavigationChild { @Composable override fun Content() = ChecksumToolsContent(component) } - class MeshGradients(val component: MeshGradientsComponent) : NavigationChild() { + class MeshGradients(private val component: MeshGradientsComponent) : NavigationChild { @Composable override fun Content() = MeshGradientsContent(component) } - class EditExif(val component: EditExifComponent) : NavigationChild() { + class EditExif(private val component: EditExifComponent) : NavigationChild { @Composable override fun Content() = EditExifContent(component) } - class ImageCutter(val component: ImageCutterComponent) : NavigationChild() { + class ImageCutter(private val component: ImageCutterComponent) : NavigationChild { @Composable override fun Content() = ImageCutterContent(component) } + class AudioCoverExtractor( + private val component: AudioCoverExtractorComponent + ) : NavigationChild { + @Composable + override fun Content() = AudioCoverExtractorContent(component) + } + } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 008a6bc83..0fc5f399b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,6 @@ konfettiCompose = "2.0.5" shadowsPlus = "1.0.4" exifinterface = "1.4.0" firebaseAnalyticsKtx = "22.3.0" -firebaseCrashlyticsGradle = "3.0.3" google-segmentationSelfie = "16.0.0-beta6" google-subjectSegmentation = "16.0.0-beta1" detekt = "1.23.8" @@ -75,13 +74,18 @@ zxingAndroidEmbedded = "4.3.0" capturable = "3.0.1" moshi = "1.15.2" aboutlibraries = "12.0.0-a02+compose_1_8" -aboutlibrariesGradle = "12.0.0-a04" junit = "4.13.2" bouncycastle = "1.80" evaluator = "1.0.0" +ffmpeg-metadata-retriever = "1.0.19" + +firebaseCrashlyticsGradle = "3.0.3" +aboutlibrariesGradle = "12.0.0-a04" moduleGraphGradle = "0.12.0" [libraries] +ffmpeg-metadata-retriever-core = { module = "com.github.wseemann:FFmpegMediaMetadataRetriever-core", version.ref = "ffmpeg-metadata-retriever" } +ffmpeg-metadata-retriever-native = { module = "com.github.wseemann:FFmpegMediaMetadataRetriever-native", version.ref = "ffmpeg-metadata-retriever" } evaluator = { module = "com.github.T8RIN:KotlinEvaluator", version.ref = "evaluator" } aboutlibraries-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlibraries" } moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5e03b67e6..7ae3009e7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -109,6 +109,7 @@ include(":feature:checksum-tools") include(":feature:mesh-gradients") include(":feature:edit-exif") include(":feature:image-cutting") +include(":feature:audio-cover-extractor") include(":feature:root")