Caching Fixes

This commit is contained in:
Shabinder Singh 2021-10-10 21:51:14 +05:30
parent 7f279f2602
commit f7e38c2c6e
13 changed files with 127 additions and 64 deletions

View File

@ -124,8 +124,9 @@ dependencies {
implementation(storage.chooser) implementation(storage.chooser)
with(bundles) { with(bundles) {
implementation(androidx.lifecycle) implementation(ktor)
implementation(mviKotlin) implementation(mviKotlin)
implementation(androidx.lifecycle)
implementation(accompanist.inset) implementation(accompanist.inset)
} }

View File

@ -43,6 +43,7 @@ import com.shabinder.common.models.event.coroutines.failure
import com.shabinder.common.providers.FetchPlatformQueryResult import com.shabinder.common.providers.FetchPlatformQueryResult
import com.shabinder.common.translations.Strings import com.shabinder.common.translations.Strings
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import io.ktor.client.HttpClient
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -62,6 +63,7 @@ class ForegroundService : LifecycleService() {
private val fetcher: FetchPlatformQueryResult by inject() private val fetcher: FetchPlatformQueryResult by inject()
private val logger: Kermit by inject() private val logger: Kermit by inject()
private val dir: FileManager by inject() private val dir: FileManager by inject()
private val httpClient: HttpClient by inject()
private var messageList = private var messageList =
java.util.Collections.synchronizedList(MutableList(5) { emptyMessage }) java.util.Collections.synchronizedList(MutableList(5) { emptyMessage })
@ -170,7 +172,7 @@ class ForegroundService : LifecycleService() {
trackStatusFlowMap[track.title] = DownloadStatus.Downloading() trackStatusFlowMap[track.title] = DownloadStatus.Downloading()
// Enqueueing Download // Enqueueing Download
downloadFile(url).collect { httpClient.downloadFile(url).collect {
when (it) { when (it) {
is DownloadResult.Error -> { is DownloadResult.Error -> {
logger.d(TAG) { it.message } logger.d(TAG) { it.message }

View File

@ -15,12 +15,19 @@ fun cleanFiles(dir: File) {
if (file.isDirectory) { if (file.isDirectory) {
cleanFiles(file) cleanFiles(file)
} else if (file.isFile) { } else if (file.isFile) {
if (file.path.toString().substringAfterLast(".") != "mp3") { val filePath = file.path.toString()
Log.d("Files Cleaning", "Cleaning ${file.path}") if (filePath.substringAfterLast(".") != "mp3" || filePath.isTempFile()) {
Log.d("Files Cleaning", "Cleaning $filePath")
file.delete() file.delete()
} }
} }
} }
} }
} catch (e: Exception) { e.printStackTrace() } } catch (e: Exception) {
e.printStackTrace()
}
}
private fun String.isTempFile(): Boolean {
return substringBeforeLast(".").takeLast(5) == ".temp"
} }

View File

@ -117,7 +117,7 @@ class AndroidFileManager(
try { try {
// Add Mp3 Tags and Add to Library // Add Mp3 Tags and Add to Library
if(trackDetails.audioFormat != AudioFormat.MP3) if (trackDetails.audioFormat != AudioFormat.MP3)
throw InvalidDataException("Audio Format is ${trackDetails.audioFormat}, Needs Conversion!") throw InvalidDataException("Audio Format is ${trackDetails.audioFormat}, Needs Conversion!")
Mp3File(File(songFile.absolutePath)) Mp3File(File(songFile.absolutePath))
@ -166,7 +166,7 @@ class AndroidFileManager(
override suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture = override suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture =
withContext(dispatcherIO) { withContext(dispatcherIO) {
val cachePath = imageCacheDir() + getNameURL(url) val cachePath = getImageCachePath(url)
Picture( Picture(
image = (loadCachedImage(cachePath, reqWidth, reqHeight) ?: freshImage( image = (loadCachedImage(cachePath, reqWidth, reqHeight) ?: freshImage(
url, url,
@ -214,7 +214,7 @@ class AndroidFileManager(
// Decode and Cache Full Sized Image in Background // Decode and Cache Full Sized Image in Background
cacheImage( cacheImage(
BitmapFactory.decodeByteArray(input, 0, input.size), BitmapFactory.decodeByteArray(input, 0, input.size),
imageCacheDir() + getNameURL(url) getImageCachePath(url)
) )
} }
bitmap // return Memory Efficient Bitmap bitmap // return Memory Efficient Bitmap

View File

@ -25,10 +25,14 @@ import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.utils.removeIllegalChars import com.shabinder.common.utils.removeIllegalChars
import com.shabinder.common.utils.requireNotNull
import com.shabinder.database.Database import com.shabinder.database.Database
import io.ktor.client.request.* import io.ktor.client.HttpClient
import io.ktor.client.statement.* import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.* import io.ktor.client.request.get
import io.ktor.client.statement.HttpStatement
import io.ktor.http.contentLength
import io.ktor.http.isSuccess
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -80,12 +84,13 @@ fun FileManager.createDirectories() {
if (!defaultDir().contains("null${fileSeparator()}SpotiFlyer")) { if (!defaultDir().contains("null${fileSeparator()}SpotiFlyer")) {
createDirectory(defaultDir()) createDirectory(defaultDir())
createDirectory(imageCacheDir()) createDirectory(imageCacheDir())
createDirectory(defaultDir() + "Tracks/") createDirectory(defaultDir() + "Tracks" + fileSeparator())
createDirectory(defaultDir() + "Albums/") createDirectory(defaultDir() + "Albums" + fileSeparator())
createDirectory(defaultDir() + "Playlists/") createDirectory(defaultDir() + "Playlists" + fileSeparator())
createDirectory(defaultDir() + "YT_Downloads/") createDirectory(defaultDir() + "YT_Downloads" + fileSeparator())
}
} catch (ignored: Exception) {
} }
} catch (ignored: Exception) { }
} }
fun FileManager.finalOutputDir( fun FileManager.finalOutputDir(
@ -100,24 +105,50 @@ fun FileManager.finalOutputDir(
removeIllegalChars(subFolder) + this.fileSeparator() removeIllegalChars(subFolder) + this.fileSeparator()
} + } +
removeIllegalChars(itemName) + extension removeIllegalChars(itemName) + extension
/*DIR Specific Operation End*/
fun getNameURL(url: String): String { fun FileManager.getImageCachePath(
return url.substring(url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1, url.length) url: String
.replace('/', '_') ): String = imageCacheDir() + getNameFromURL(url, isImage = true)
/*DIR Specific Operation End*/
private fun getNameFromURL(url: String, isImage: Boolean = false): String {
val startIndex = url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1
var fileName = if (startIndex != -1)
url.substring(startIndex).replace('/', '_')
else url.substringAfterLast("/")
// Generify File Extensions
if (isImage) {
if (fileName.length - fileName.lastIndexOf(".") > 5) {
fileName += ".jpeg"
} else {
if (fileName.endsWith(".jpg"))
fileName = fileName.substringBeforeLast(".") + ".jpeg"
}
}
return fileName
} }
suspend fun downloadFile(url: String): Flow<DownloadResult> { suspend fun HttpClient.downloadFile(url: String) = downloadFile(url, this)
suspend fun downloadFile(url: String, client: HttpClient? = null): Flow<DownloadResult> {
return flow { return flow {
val client = createHttpClient() val httpClient = client ?: createHttpClient()
val response = client.get<HttpStatement>(url).execute() val response = httpClient.get<HttpStatement>(url).execute()
val data = ByteArray(response.contentLength()!!.toInt()) // Not all requests return Content Length
val data = kotlin.runCatching {
ByteArray(response.contentLength().requireNotNull().toInt())
}.getOrNull() ?: byteArrayOf()
var offset = 0 var offset = 0
do { do {
// Set Length optimally, after how many kb you want a progress update, now it 0.25mb // Set Length optimally, after how many kb you want a progress update, now it 0.25mb
val currentRead = response.content.readAvailable(data, offset, 2_50_000) val currentRead = response.content.readAvailable(data, offset, 2_50_000)
offset += currentRead offset += currentRead
val progress = (offset * 100f / data.size).roundToInt() val progress = data.size.takeIf { it != 0 }?.let { fileSize ->
(offset * 100f / fileSize).roundToInt()
} ?: 0
emit(DownloadResult.Progress(progress)) emit(DownloadResult.Progress(progress))
} while (currentRead > 0) } while (currentRead > 0)
if (response.status.isSuccess()) { if (response.status.isSuccess()) {
@ -125,7 +156,10 @@ suspend fun downloadFile(url: String): Flow<DownloadResult> {
} else { } else {
emit(DownloadResult.Error("File not downloaded")) emit(DownloadResult.Error("File not downloaded"))
} }
client.close()
// Close Client if We Created One
if (client == null)
httpClient.close()
}.catch { e -> }.catch { e ->
e.printStackTrace() e.printStackTrace()
emit(DownloadResult.Error(e.message ?: "File not downloaded")) emit(DownloadResult.Error(e.message ?: "File not downloaded"))

View File

@ -178,8 +178,7 @@ class DesktopFileManager(
override fun addToLibrary(path: String) {} override fun addToLibrary(path: String) {}
override suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture { override suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture {
val cachePath = imageCacheDir() + getNameURL(url) var picture: ImageBitmap? = loadCachedImage(getImageCachePath(url), reqWidth, reqHeight)
var picture: ImageBitmap? = loadCachedImage(cachePath, reqWidth, reqHeight)
if (picture == null) picture = freshImage(url, reqWidth, reqHeight) if (picture == null) picture = freshImage(url, reqWidth, reqHeight)
return Picture(image = picture) return Picture(image = picture)
} }
@ -208,7 +207,7 @@ class DesktopFileManager(
if (result != null) { if (result != null) {
GlobalScope.launch(Dispatchers.IO) { // TODO Refactor GlobalScope.launch(Dispatchers.IO) { // TODO Refactor
cacheImage(result, imageCacheDir() + getNameURL(url)) cacheImage(result, getImageCachePath(url))
} }
result.toImageBitmap() result.toImageBitmap()
} else null } else null

View File

@ -20,7 +20,7 @@ data class SaavnSong @OptIn(ExperimentalSerializationApi::class) constructor(
// val explicit_content: Int = 0, // val explicit_content: Int = 0,
val has_lyrics: Boolean = false, val has_lyrics: Boolean = false,
val id: String, val id: String,
val image: String, val image: String = "",
val label: String? = null, val label: String? = null,
val label_url: String? = null, val label_url: String? = null,
val language: String, val language: String,

View File

@ -19,6 +19,7 @@ package com.shabinder.common.providers.gaana
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir import com.shabinder.common.core_components.file_manager.finalOutputDir
import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException
@ -124,8 +125,7 @@ class GaanaProvider(
title = it.track_title, title = it.track_title,
artists = it.artist.map { artist -> artist?.name.toString() }, artists = it.artist.map { artist -> artist?.name.toString() },
durationSec = it.duration, durationSec = it.duration,
albumArtPath = fileManager.imageCacheDir() + (it.artworkLink.substringBeforeLast('/') albumArtPath = fileManager.getImageCachePath(it.artworkLink),
.substringAfterLast('/')) + ".jpeg",
albumName = it.album_title, albumName = it.album_title,
genre = it.genre?.mapNotNull { genre -> genre?.name } ?: emptyList(), genre = it.genre?.mapNotNull { genre -> genre?.name } ?: emptyList(),
year = it.release_date, year = it.release_date,

View File

@ -3,6 +3,7 @@ package com.shabinder.common.providers.saavn
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir import com.shabinder.common.core_components.file_manager.finalOutputDir
import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.models.* import com.shabinder.common.models.*
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.saavn.SaavnSong import com.shabinder.common.models.saavn.SaavnSong
@ -68,7 +69,7 @@ class SaavnProvider(
artists = it.artistMap.keys.toMutableSet().apply { addAll(it.singers.split(",")) }.toList(), artists = it.artistMap.keys.toMutableSet().apply { addAll(it.singers.split(",")) }.toList(),
durationSec = it.duration.toInt(), durationSec = it.duration.toInt(),
albumName = it.album, albumName = it.album,
albumArtPath = fileManager.imageCacheDir() + (it.image.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg", albumArtPath = fileManager.getImageCachePath(it.image),
year = it.year, year = it.year,
comment = it.copyright_text, comment = it.copyright_text,
trackUrl = it.perma_url, trackUrl = it.perma_url,

View File

@ -3,6 +3,7 @@ package com.shabinder.common.providers.sound_cloud
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir import com.shabinder.common.core_components.file_manager.finalOutputDir
import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.models.AudioFormat import com.shabinder.common.models.AudioFormat
import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
@ -70,9 +71,7 @@ class SoundCloudProvider(
artists = /*it.artists?.map { artist -> artist?.name.toString() } ?:*/ listOf(it.user.username.ifBlank { it.genre }), artists = /*it.artists?.map { artist -> artist?.name.toString() } ?:*/ listOf(it.user.username.ifBlank { it.genre }),
albumArtists = /*it.album?.artists?.mapNotNull { artist -> artist?.name } ?:*/ emptyList(), albumArtists = /*it.album?.artists?.mapNotNull { artist -> artist?.name } ?:*/ emptyList(),
durationSec = (it.duration / 1000), durationSec = (it.duration / 1000),
albumArtPath = fileManager.imageCacheDir() + (it.artworkUrl.formatArtworkUrl()).substringAfterLast( albumArtPath = fileManager.getImageCachePath(it.artworkUrl.formatArtworkUrl()),
'/'
) + ".jpeg",
albumName = "", //it.album?.name, albumName = "", //it.album?.name,
year = runCatching { it.displayDate.substring(0, 4) }.getOrNull(), year = runCatching { it.displayDate.substring(0, 4) }.getOrNull(),
comment = it.caption, comment = it.caption,

View File

@ -19,6 +19,7 @@ package com.shabinder.common.providers.spotify
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir import com.shabinder.common.core_components.file_manager.finalOutputDir
import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.core_components.utils.createHttpClient import com.shabinder.common.core_components.utils.createHttpClient
import com.shabinder.common.models.* import com.shabinder.common.models.*
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
@ -201,9 +202,7 @@ class SpotifyProvider(
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(), artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
albumArtists = it.album?.artists?.mapNotNull { artist -> artist?.name } ?: emptyList(), albumArtists = it.album?.artists?.mapNotNull { artist -> artist?.name } ?: emptyList(),
durationSec = (it.duration_ms / 1000).toInt(), durationSec = (it.duration_ms / 1000).toInt(),
albumArtPath = fileManager.imageCacheDir() + (it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast( albumArtPath = fileManager.getImageCachePath(it.album?.images?.firstOrNull()?.url ?: ""),
'/'
) + ".jpeg",
albumName = it.album?.name, albumName = it.album?.name,
year = it.album?.release_date, year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}", comment = "Genres:${it.album?.genres?.joinToString()}",

View File

@ -19,6 +19,7 @@ package com.shabinder.common.providers.youtube
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir import com.shabinder.common.core_components.file_manager.finalOutputDir
import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException
@ -30,7 +31,7 @@ import io.github.shabinder.YoutubeDownloader
import io.github.shabinder.models.YoutubeVideo import io.github.shabinder.models.YoutubeVideo
import io.github.shabinder.models.formats.Format import io.github.shabinder.models.formats.Format
import io.github.shabinder.models.quality.AudioQuality import io.github.shabinder.models.quality.AudioQuality
import io.ktor.client.* import io.ktor.client.HttpClient
class YoutubeProvider( class YoutubeProvider(
private val httpClient: HttpClient, private val httpClient: HttpClient,
@ -108,13 +109,14 @@ class YoutubeProvider(
title = name title = name
trackList = videos.map { trackList = videos.map {
val imageURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg"
TrackDetails( TrackDetails(
title = it.title ?: "N/A", title = it.title ?: "N/A",
artists = listOf(it.author ?: "N/A"), artists = listOf(it.author ?: "N/A"),
durationSec = it.lengthSeconds, durationSec = it.lengthSeconds,
albumArtPath = fileManager.imageCacheDir() + it.videoId + ".jpeg", albumArtPath = fileManager.getImageCachePath(imageURL),
source = Source.YouTube, source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg", albumArtURL = imageURL,
downloaded = if (fileManager.isPresent( downloaded = if (fileManager.isPresent(
fileManager.finalOutputDir( fileManager.finalOutputDir(
itemName = it.title ?: "N/A", itemName = it.title ?: "N/A",
@ -155,7 +157,7 @@ class YoutubeProvider(
val video = ytDownloader.getVideo(searchId) val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg" coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video.videoDetails val detail = video.videoDetails
val name = detail.title?.replace(detail.author?.uppercase() ?: "", "", true) val name = detail.title?.replace(detail.author?.toUpperCase() ?: "", "", true)
?: detail.title ?: "" ?: detail.title ?: ""
// logger.i{ detail.toString() } // logger.i{ detail.toString() }
trackList = listOf( trackList = listOf(
@ -163,9 +165,9 @@ class YoutubeProvider(
title = name, title = name,
artists = listOf(detail.author ?: "N/A"), artists = listOf(detail.author ?: "N/A"),
durationSec = detail.lengthSeconds, durationSec = detail.lengthSeconds,
albumArtPath = fileManager.imageCacheDir() + "$searchId.jpeg", albumArtPath = fileManager.getImageCachePath(coverUrl),
source = Source.YouTube, source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", albumArtURL = coverUrl,
downloaded = if (fileManager.isPresent( downloaded = if (fileManager.isPresent(
fileManager.finalOutputDir( fileManager.finalOutputDir(
itemName = name, itemName = name,
@ -179,7 +181,12 @@ class YoutubeProvider(
else { else {
DownloadStatus.NotDownloaded DownloadStatus.NotDownloaded
}, },
outputFilePath = fileManager.finalOutputDir(name, folderType, subFolder, fileManager.defaultDir()/*,".m4a"*/), outputFilePath = fileManager.finalOutputDir(
name,
folderType,
subFolder,
fileManager.defaultDir()/*,".m4a"*/
),
videoID = searchId videoID = searchId
) )
) )

View File

@ -18,19 +18,33 @@ package com.shabinder.common.providers.youtube_music
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.models.* import com.shabinder.common.models.AudioFormat
import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.YoutubeTrack
import com.shabinder.common.models.corsApi
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.flatMap import com.shabinder.common.models.event.coroutines.flatMap
import com.shabinder.common.models.event.coroutines.flatMapError
import com.shabinder.common.models.event.coroutines.map import com.shabinder.common.models.event.coroutines.map
import com.shabinder.common.providers.youtube.YoutubeProvider import com.shabinder.common.providers.youtube.YoutubeProvider
import com.shabinder.common.providers.youtube.get
import com.shabinder.common.providers.youtube_to_mp3.requests.YoutubeMp3 import com.shabinder.common.providers.youtube_to_mp3.requests.YoutubeMp3
import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.request.* import io.ktor.client.request.headers
import io.ktor.http.* import io.ktor.client.request.post
import kotlinx.serialization.json.* import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import kotlin.collections.set import kotlin.collections.set
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -50,7 +64,7 @@ class YoutubeMusic constructor(
suspend fun findMp3SongDownloadURLYT( suspend fun findMp3SongDownloadURLYT(
trackDetails: TrackDetails, trackDetails: TrackDetails,
preferredQuality: AudioQuality = fileManager.preferenceManager.audioQuality preferredQuality: AudioQuality = fileManager.preferenceManager.audioQuality
): SuspendableEvent<Pair<String,AudioQuality>, Throwable> { ): SuspendableEvent<Pair<String, AudioQuality>, Throwable> {
return getYTIDBestMatch(trackDetails).flatMap { videoID -> return getYTIDBestMatch(trackDetails).flatMap { videoID ->
// As YT compress Audio hence there is no benefit of quality for more than 192 // As YT compress Audio hence there is no benefit of quality for more than 192
val optimalQuality = val optimalQuality =
@ -69,7 +83,7 @@ class YoutubeMusic constructor(
} }
}*/.map { }*/.map {
trackDetails.audioFormat = AudioFormat.MP3 trackDetails.audioFormat = AudioFormat.MP3
Pair(it,optimalQuality) Pair(it, optimalQuality)
} }
} }
} }
@ -168,7 +182,7 @@ class YoutubeMusic constructor(
! 4 - Duration (hh:mm:ss) ! 4 - Duration (hh:mm:ss)
! !
! We blindly gather all the details we get our hands on, then ! We blindly gather all the details we get our hands on, then
! cherry pick the details we need based on their index numbers, ! cherry-pick the details we need based on their index numbers,
! we do so only if their Type is 'Song' or 'Video ! we do so only if their Type is 'Song' or 'Video
*/ */
@ -180,7 +194,7 @@ class YoutubeMusic constructor(
/* /*
Filter Out dummies here itself Filter Out dummies here itself
! 'musicResponsiveListItemFlexColumnRenderer' should have more that one ! 'musicResponsiveListItemFlexColumnRenderer' should have more that one
! sub-block, if not its a dummy, why does the YTM response contain dummies? ! sub-block, if not it is a dummy, why does the YTM response contain dummies?
! I have no clue. We skip these. ! I have no clue. We skip these.
! Remember that we appended the linkBlock to result, treating that like the ! Remember that we appended the linkBlock to result, treating that like the
@ -189,7 +203,7 @@ class YoutubeMusic constructor(
*/ */
for (detailArray in result.subList(0, result.size - 1)) { for (detailArray in result.subList(0, result.size - 1)) {
for (detail in detailArray.jsonArray) { for (detail in detailArray.jsonArray) {
if (detail.jsonObject["musicResponsiveListItemFlexColumnRenderer"]?.jsonObject?.size ?: 0 < 2) continue if ((detail.jsonObject["musicResponsiveListItemFlexColumnRenderer"]?.jsonObject?.size ?: 0) < 2) continue
// if not a dummy, collect All Variables // if not a dummy, collect All Variables
val details = val details =
@ -262,8 +276,8 @@ class YoutubeMusic constructor(
// most song results on youtube go by $artist - $songName or artist1/artist2 // most song results on youtube go by $artist - $songName or artist1/artist2
var hasCommonWord = false var hasCommonWord = false
val resultName = result.name?.lowercase()?.replace("-", " ")?.replace("/", " ") ?: "" val resultName = result.name?.toLowerCase()?.replace("-", " ")?.replace("/", " ") ?: ""
val trackNameWords = trackName.lowercase().split(" ") val trackNameWords = trackName.toLowerCase().split(" ")
for (nameWord in trackNameWords) { for (nameWord in trackNameWords) {
if (nameWord.isNotBlank() && FuzzySearch.partialRatio( if (nameWord.isNotBlank() && FuzzySearch.partialRatio(
@ -287,8 +301,8 @@ class YoutubeMusic constructor(
if (result.type == "Song") { if (result.type == "Song") {
for (artist in trackArtists) { for (artist in trackArtists) {
if (FuzzySearch.ratio( if (FuzzySearch.ratio(
artist.lowercase(), artist.toLowerCase(),
result.artist?.lowercase() ?: "" result.artist?.toLowerCase() ?: ""
) > 85 ) > 85
) )
artistMatchNumber++ artistMatchNumber++
@ -296,8 +310,8 @@ class YoutubeMusic constructor(
} else { // i.e. is a Video } else { // i.e. is a Video
for (artist in trackArtists) { for (artist in trackArtists) {
if (FuzzySearch.partialRatio( if (FuzzySearch.partialRatio(
artist.lowercase(), artist.toLowerCase(),
result.name?.lowercase() ?: "" result.name?.toLowerCase() ?: ""
) > 85 ) > 85
) )
artistMatchNumber++ artistMatchNumber++