From 50fa91fbca2d559fcac008bd6fdf253ddc7147ca Mon Sep 17 00:00:00 2001 From: shabinder Date: Mon, 15 Feb 2021 03:34:37 +0530 Subject: [PATCH] WIP:Download Impl --- .../list/integration/SpotiFlyerListImpl.kt | 1 + .../list/store/SpotiFlyerListStoreProvider.kt | 7 +- .../shabinder/common/main/SpotiFlyerMainUi.kt | 36 ++++----- .../shabinder/common/models/DownloadObject.kt | 17 ++-- .../com/shabinder/common/di/AndroidActual.kt | 15 +++- .../com/shabinder/common/di/ID3Tagging.kt | 6 +- .../shabinder/common/di/YoutubeProvider.kt | 4 +- .../kotlin/com/shabinder/common/di/DI.kt | 2 +- .../kotlin/com/shabinder/common/di/Dir.kt | 5 +- .../kotlin/com/shabinder/common/di/Expect.kt | 6 +- .../common/di/FetchPlatformQueryResult.kt | 2 + .../kotlin/com/shabinder/common/di/Utils.kt | 2 + .../common/di/providers/GaanaProvider.kt | 2 +- .../common/di/providers/SpotifyProvider.kt | 4 +- .../common/di/providers/YoutubeMusic.kt | 10 +++ .../com/shabinder/common/di/DesktopActual.kt | 79 ++++++++++++++++++- .../common/di/{Dir.kt => DesktopDir.kt} | 5 +- .../com/shabinder/common/di/ID3Tagging.kt | 6 +- .../shabinder/common/di/YoutubeProvider.kt | 4 +- .../resources/drawable/ic_download_arrow.xml | 2 +- 20 files changed, 158 insertions(+), 57 deletions(-) rename common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/{Dir.kt => DesktopDir.kt} (97%) diff --git a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt index f64cb4ad..5c7232a3 100644 --- a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt +++ b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt @@ -20,6 +20,7 @@ internal class SpotiFlyerListImpl( private val store = instanceKeeper.getStore { SpotiFlyerListStoreProvider( + dir = this.dir, storeFactory = storeFactory, fetchQuery = fetchQuery, link = link diff --git a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt index e79165d3..b94a8c9d 100644 --- a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt +++ b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt @@ -2,6 +2,7 @@ package com.shabinder.common.list.store import com.arkivanov.mvikotlin.core.store.* import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor +import com.shabinder.common.di.Dir import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.downloadTracks import com.shabinder.common.list.SpotiFlyerList.State @@ -9,8 +10,10 @@ import com.shabinder.common.list.store.SpotiFlyerListStore.Intent import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.TrackDetails +import com.shabinder.common.ui.showPopUpMessage internal class SpotiFlyerListStoreProvider( + private val dir: Dir, private val storeFactory: StoreFactory, private val fetchQuery: FetchPlatformQueryResult, private val link: String @@ -45,8 +48,8 @@ internal class SpotiFlyerListStoreProvider( is Intent.StartDownloadAll -> { val finalList = intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded } - if (finalList.isNullOrEmpty()) //TODO showDialog("All Songs are Processed") - else downloadTracks(finalList) + if (finalList.isNullOrEmpty()) showPopUpMessage("All Songs are Processed") + else downloadTracks(finalList,fetchQuery.youtubeMusic::getYTIDBestMatch,dir::saveFileWithMetadata) val list = intent.trackList.map { if (it.downloaded == DownloadStatus.NotDownloaded) { diff --git a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt index 54b677cb..843f227a 100644 --- a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt +++ b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt @@ -25,9 +25,11 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.shabinder.common.di.giveDonation import com.shabinder.common.models.DownloadRecord import com.shabinder.common.main.SpotiFlyerMain.HomeCategory import com.shabinder.common.di.openPlatform +import com.shabinder.common.di.shareApp import com.shabinder.common.ui.* import com.shabinder.common.ui.SpotiFlyerTypography @@ -73,10 +75,9 @@ fun HomeTabBar( ) } - @Suppress("USELESS_CAST")//Showing Error in Latest Android Studio Canary TabRow( selectedTabIndex = selectedIndex, - indicator = indicator as @Composable (List) -> Unit, + indicator = indicator, modifier = modifier, ) { categories.forEachIndexed { index, category -> @@ -245,51 +246,44 @@ fun AboutColumn(modifier: Modifier = Modifier) { ) } } - /*Row( + Row( modifier = modifier.fillMaxWidth().padding(vertical = 6.dp) - .clickable(onClick = { startPayment(mainActivity) }), + .clickable(onClick = { giveDonation() }), verticalAlignment = Alignment.CenterVertically ) { - Icon(Icons.Rounded.MailOutline.copy(defaultHeight = 32.dp,defaultWidth = 32.dp)) + Icon(Icons.Rounded.MailOutline,"Support Developer") Spacer(modifier = Modifier.padding(start = 16.dp)) Column { Text( - text = stringResource(R.string.donate), + text = "Donate", style = SpotiFlyerTypography.h6 ) Text( - text = stringResource(R.string.donate_subtitle), + text = "If you think I deserve to get paid for my work, you can leave me some money here.", style = SpotiFlyerTypography.subtitle2 ) } - }*/ - /*Row( + } + Row( modifier = modifier.fillMaxWidth().padding(vertical = 6.dp) .clickable(onClick = { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, "Hey, checkout this excellent Music Downloader http://github.com/Shabinder/SpotiFlyer") - type = "text/plain" - } - - val shareIntent = Intent.createChooser(sendIntent, null) - ctx.startActivity(shareIntent) + shareApp() }), verticalAlignment = Alignment.CenterVertically ) { - Icon(Icons.Rounded.Share.copy(defaultHeight = 32.dp,defaultWidth = 32.dp)) + Icon(Icons.Rounded.Share,"Share SpotiFlyer App") Spacer(modifier = Modifier.padding(start = 16.dp)) Column { Text( - text = stringResource(R.string.share), + text = "Share", style = SpotiFlyerTypography.h6 ) Text( - text = stringResource(R.string.share_subtitle), + text = "Share this app with your friends and family.", style = SpotiFlyerTypography.subtitle2 ) } - }*/ + } } } } diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt index 065a8681..dfc0f829 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt @@ -34,15 +34,16 @@ data class TrackDetails( var source: Source, var downloaded: DownloadStatus = DownloadStatus.NotDownloaded, var progress: Int = 2,//2 for visual progress bar hint - var outputFile: String, + var outputFilePath: String, var videoID:String? = null ) -enum class DownloadStatus{ - Downloaded, - Downloading, - Queued, - NotDownloaded, - Converting, - Failed +@Serializable +sealed class DownloadStatus { + object Downloaded :DownloadStatus() + data class Downloading(val progress: Int = 0):DownloadStatus() + object Queued :DownloadStatus() + object NotDownloaded :DownloadStatus() + object Converting :DownloadStatus() + object Failed :DownloadStatus() } \ No newline at end of file diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt index 8ef07e77..5671571f 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt @@ -22,13 +22,24 @@ actual fun openPlatform(packageID:String, platformLink:String){ } actual fun shareApp(){ - //TODO + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, "Hey, checkout this excellent Music Downloader http://github.com/Shabinder/SpotiFlyer") + type = "text/plain" + } + + val shareIntent = Intent.createChooser(sendIntent, null) + appContext.startActivity(shareIntent) } actual fun giveDonation(){ //TODO } -actual fun downloadTracks(list: List){ +actual suspend fun downloadTracks( + list: List, + getYTIDBestMatch:suspend (String,TrackDetails)->String?, + saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit +){ //TODO } \ No newline at end of file diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/ID3Tagging.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/ID3Tagging.kt index 37950c03..e6d24429 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/ID3Tagging.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/ID3Tagging.kt @@ -32,7 +32,7 @@ fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File { } @Suppress("BlockingMethodInNonBlockingContext") -suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails, filePath:String){ +suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails){ val id3v2Tag = ID3v24Tag().apply { artist = track.artists.joinToString(",") title = track.title @@ -50,7 +50,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails, filePath:String fis.close() id3v2Tag.setAlbumImage(bytesArray, "image/jpeg") this.id3v2Tag = id3v2Tag - saveFile(filePath) + saveFile(track.outputFilePath) }catch (e: java.io.FileNotFoundException){ try { //Image Still Not Downloaded! @@ -61,7 +61,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails, filePath:String is DownloadResult.Success -> { id3v2Tag.setAlbumImage(it.byteArray, "image/jpeg") this.id3v2Tag = id3v2Tag - saveFile(filePath) + saveFile(track.outputFilePath) } is DownloadResult.Progress -> {}//Nothing for Now , no progress bar to show } diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt index bb7e5293..cc9bf9fd 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt @@ -125,7 +125,7 @@ actual class YoutubeProvider actual constructor( else { DownloadStatus.NotDownloaded }, - outputFile = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir(),".m4a"), + outputFilePath = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir(),".m4a"), videoID = it.videoId() ) } @@ -195,7 +195,7 @@ actual class YoutubeProvider actual constructor( else { DownloadStatus.NotDownloaded }, - outputFile = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir(),".m4a"), + outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir(),".m4a"), videoID = searchId ) ) diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt index 5b166847..ada4602e 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt @@ -33,7 +33,7 @@ fun commonModule(enableNetworkLogs: Boolean) = module { single { SpotifyProvider(get(),get(),get(),get()) } single { GaanaProvider(get(),get(),get(),get()) } single { YoutubeProvider(get(),get(),get(),get()) } - single { FetchPlatformQueryResult(get(),get(),get(),get()) } + single { FetchPlatformQueryResult(get(),get(),get(),get(),get()) } single { createHttpClient(enableNetworkLogs = enableNetworkLogs) } } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt index ffb025df..3badb925 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt @@ -2,6 +2,7 @@ package com.shabinder.common.di import androidx.compose.ui.graphics.ImageBitmap import co.touchlab.kermit.Kermit +import com.shabinder.common.di.providers.YoutubeMusic import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.TrackDetails import io.ktor.client.request.* @@ -13,7 +14,7 @@ import kotlinx.coroutines.flow.flow import kotlin.math.roundToInt expect class Dir( - logger: Kermit, + logger: Kermit ) { fun isPresent(path:String):Boolean fun fileSeparator(): String @@ -23,7 +24,7 @@ expect class Dir( suspend fun cacheImage(image: Any,path: String) // in Android = ImageBitmap, Desktop = BufferedImage suspend fun loadImage(url:String): ImageBitmap? suspend fun clearCache() - suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, path: String, trackDetails: TrackDetails) + suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, trackDetails: TrackDetails) } suspend fun downloadFile(url: String): Flow { diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt index b738fb65..5697f0e4 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt @@ -8,4 +8,8 @@ expect fun shareApp() expect fun giveDonation() -expect fun downloadTracks(list: List) \ No newline at end of file +expect suspend fun downloadTracks( + list: List, + getYTIDBestMatch:suspend (String,TrackDetails)->String?, + saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit +) \ No newline at end of file diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt index f7d07484..86f1c8c1 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt @@ -4,6 +4,7 @@ import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.database.DownloadRecordDatabaseQueries import com.shabinder.common.di.providers.GaanaProvider import com.shabinder.common.di.providers.SpotifyProvider +import com.shabinder.common.di.providers.YoutubeMusic import com.shabinder.database.Database import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -12,6 +13,7 @@ class FetchPlatformQueryResult( private val gaanaProvider: GaanaProvider, private val spotifyProvider: SpotifyProvider, private val youtubeProvider: YoutubeProvider, + val youtubeMusic: YoutubeMusic, private val database: Database ) { private val db:DownloadRecordDatabaseQueries diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Utils.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Utils.kt index a2773167..cdd47a1d 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Utils.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Utils.kt @@ -1,5 +1,7 @@ package com.shabinder.common.di + + /** * Removing Illegal Chars from File Name * **/ diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt index 7273f42d..fa3e065f 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt @@ -220,7 +220,7 @@ class GaanaProvider( downloaded = it.downloaded ?: DownloadStatus.NotDownloaded, source = Source.Gaana, albumArtURL = it.artworkLink, - outputFile = dir.finalOutputDir(it.track_title,type, subFolder,dir.defaultDir(),".m4a") + outputFilePath = dir.finalOutputDir(it.track_title,type, subFolder,dir.defaultDir(),".m4a") ) } } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt index 50b01c58..2bbc1cd5 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt @@ -23,7 +23,6 @@ import com.shabinder.common.di.TokenStore import com.shabinder.common.di.finalOutputDir import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.di.spotify.SpotifyRequests -import com.shabinder.common.di.spotify.authenticateSpotify import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.spotify.Album @@ -35,7 +34,6 @@ import io.ktor.client.* import io.ktor.client.features.* import io.ktor.client.features.json.* import io.ktor.client.request.* -import io.ktor.http.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -273,7 +271,7 @@ class SpotifyProvider( downloaded = it.downloaded, source = Source.Spotify, albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(), - outputFile = dir.finalOutputDir(it.name.toString(),type, subFolder,dir.defaultDir(),".m4a") + outputFilePath = dir.finalOutputDir(it.name.toString(),type, subFolder,dir.defaultDir(),".m4a") ) } } \ No newline at end of file diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt index 8f4961b0..93a7632b 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt @@ -1,6 +1,7 @@ package com.shabinder.common.di.providers import co.touchlab.kermit.Logger +import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.YoutubeTrack import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch import io.ktor.client.* @@ -16,6 +17,15 @@ class YoutubeMusic constructor( private val httpClient:HttpClient, ) { private val tag = "YT Music" + + suspend fun getYTIDBestMatch(query: String,trackDetails: TrackDetails):String?{ + return sortByBestMatch( + getYTTracks(query), + trackName = trackDetails.title, + trackArtists = trackDetails.artists, + trackDurationSec = trackDetails.durationSec + ).keys.firstOrNull() + } suspend fun getYTTracks(query: String):List{ val youtubeTracks = mutableListOf() diff --git a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt index 8b6efa5e..984bbdd9 100644 --- a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt +++ b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt @@ -1,6 +1,16 @@ package com.shabinder.common.di +import com.github.kiulian.downloader.YoutubeDownloader +import com.github.kiulian.downloader.model.YoutubeVideo +import com.github.kiulian.downloader.model.formats.Format +import com.github.kiulian.downloader.model.quality.AudioQuality +import com.shabinder.common.models.DownloadResult +import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch actual fun openPlatform(packageID:String, platformLink:String){ //TODO @@ -14,6 +24,71 @@ actual fun giveDonation(){ //TODO } -actual fun downloadTracks(list: List){ - //TODO +val DownloadProgressFlow = MutableStateFlow(Pair("",DownloadStatus.Queued)) + +actual suspend fun downloadTracks( + list: List, + getYTIDBestMatch:suspend (String,TrackDetails)->String?, + saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit +){ + list.forEach { + if (!it.videoID.isNullOrBlank()) {//Video ID already known! + downloadTrack(it.videoID!!, it,saveFileWithMetaData) + } else { + val searchQuery = "${it.title} - ${it.artists.joinToString(",")}" + val videoId = getYTIDBestMatch(searchQuery,it) + if (videoId.isNullOrBlank()) { + DownloadProgressFlow.emit(Pair(it.title,DownloadStatus.Failed)) + } else {//Found Youtube Video ID + downloadTrack(videoId, it,saveFileWithMetaData) + } + } + } +} + +val ytDownloader = YoutubeDownloader() + +suspend fun downloadTrack( + videoID: String, + trackDetails: TrackDetails, + saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit +) { + try { + val audioData = ytDownloader.getVideo(videoID).getData() + + audioData?.let { format -> + val url: String = format.url() + downloadFile(url).collect { + when(it){ + is DownloadResult.Error -> { + //TODO() + } + is DownloadResult.Progress -> { + DownloadProgressFlow.emit(Pair(trackDetails.title,DownloadStatus.Downloading(it.progress))) + } + is DownloadResult.Success -> { + saveFileWithMetaData(it.byteArray,trackDetails) + DownloadProgressFlow.emit(Pair(trackDetails.title,DownloadStatus.Downloaded)) + } + } + } + } + }catch (e: java.lang.Exception){ + e.printStackTrace() + } +} +fun YoutubeVideo.getData(): Format?{ + return try { + findAudioWithQuality(AudioQuality.medium)?.get(0) as Format + } catch (e: java.lang.IndexOutOfBoundsException) { + try { + findAudioWithQuality(AudioQuality.high)?.get(0) as Format + } catch (e: java.lang.IndexOutOfBoundsException) { + try { + findAudioWithQuality(AudioQuality.low)?.get(0) as Format + } catch (e: java.lang.IndexOutOfBoundsException) { + null + } + } + } } \ No newline at end of file diff --git a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/Dir.kt b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopDir.kt similarity index 97% rename from common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/Dir.kt rename to common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopDir.kt index 836f6302..4d1c873d 100644 --- a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/Dir.kt +++ b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopDir.kt @@ -61,16 +61,15 @@ actual class Dir actual constructor(private val logger: Kermit) { @Suppress("BlockingMethodInNonBlockingContext") actual suspend fun saveFileWithMetadata( mp3ByteArray: ByteArray, - path: String, trackDetails: TrackDetails ) { - val file = File(path) + val file = File(trackDetails.outputFilePath) file.writeBytes(mp3ByteArray) Mp3File(file) .removeAllTags() .setId3v1Tags(trackDetails) - .setId3v2TagsAndSaveFile(trackDetails,path) + .setId3v2TagsAndSaveFile(trackDetails) } actual suspend fun loadImage(url: String): ImageBitmap? { diff --git a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/ID3Tagging.kt b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/ID3Tagging.kt index 46f484eb..a1a4ef51 100644 --- a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/ID3Tagging.kt +++ b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/ID3Tagging.kt @@ -33,7 +33,7 @@ fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File { } @Suppress("BlockingMethodInNonBlockingContext") -suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails, filePath:String){ +suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails){ val id3v2Tag = ID3v24Tag().apply { artist = track.artists.joinToString(",") title = track.title @@ -51,7 +51,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails, filePath:String fis.close() id3v2Tag.setAlbumImage(bytesArray, "image/jpeg") this.id3v2Tag = id3v2Tag - saveFile(filePath) + saveFile(track.outputFilePath) }catch (e: java.io.FileNotFoundException){ try { //Image Still Not Downloaded! @@ -62,7 +62,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails, filePath:String is DownloadResult.Success -> { id3v2Tag.setAlbumImage(it.byteArray, "image/jpeg") this.id3v2Tag = id3v2Tag - saveFile(filePath) + saveFile(track.outputFilePath) } is DownloadResult.Progress -> {}//Nothing for Now , no progress bar to show } diff --git a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt index 0614bf88..eb0e2b4e 100644 --- a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt +++ b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/YoutubeProvider.kt @@ -126,7 +126,7 @@ actual class YoutubeProvider actual constructor( else { DownloadStatus.NotDownloaded }, - outputFile = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir(),".m4a"), + outputFilePath = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir(),".m4a"), videoID = it.videoId() ) } @@ -196,7 +196,7 @@ actual class YoutubeProvider actual constructor( else { DownloadStatus.NotDownloaded }, - outputFile = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir(),".m4a"), + outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir(),".m4a"), videoID = searchId ) ) diff --git a/desktop/src/jvmMain/resources/drawable/ic_download_arrow.xml b/desktop/src/jvmMain/resources/drawable/ic_download_arrow.xml index 4a92ae81..deadedca 100644 --- a/desktop/src/jvmMain/resources/drawable/ic_download_arrow.xml +++ b/desktop/src/jvmMain/resources/drawable/ic_download_arrow.xml @@ -21,6 +21,6 @@ android:viewportHeight="24" android:tint="?attr/colorControlNormal">