fun's: Mp3 Tagging File Downloading,etc

This commit is contained in:
shabinder 2021-02-05 21:14:09 +05:30
parent c7c61e51d6
commit 3ae3b404b1
21 changed files with 588 additions and 88 deletions

View File

@ -5,12 +5,14 @@ import androidx.compose.foundation.layout.preferredHeight
import androidx.compose.foundation.layout.preferredWidth
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp
import androidx.core.net.toUri
import com.shabinder.common.database.appContext
import dev.chrisbanes.accompanist.coil.CoilImage
@Composable
@ -29,3 +31,16 @@ actual fun ImageLoad(
modifier = modifier
)
}
@Composable
actual fun Toast(
text: String,
visibility: MutableState<Boolean>,
duration: ToastDuration
){
//We Have Android's Implementation of Toast so its just Empty
}
actual fun showPopUpMessage(text: String){
android.widget.Toast.makeText(appContext,text, android.widget.Toast.LENGTH_SHORT).show()
}

View File

@ -12,3 +12,5 @@ expect fun ImageLoad(
errorResource: ImageBitmap? = null,
modifier: Modifier = Modifier
)
expect fun showPopUpMessage(text: String)

View File

@ -0,0 +1,16 @@
package com.shabinder.common.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
enum class ToastDuration(val value: Int) {
Short(1000), Long(3000)
}
@Composable
expect fun Toast(
text: String,
visibility: MutableState<Boolean> = mutableStateOf(false),
duration: ToastDuration = ToastDuration.Long
)

View File

@ -0,0 +1,70 @@
package com.shabinder.common.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private val message: MutableState<String> = mutableStateOf("")
private val state: MutableState<Boolean> = mutableStateOf(false)
actual fun showPopUpMessage(text: String) {
message.value = text
state.value = true
}
private var isShown: Boolean = false
@Composable
actual fun Toast(
text: String,
visibility: MutableState<Boolean>,
duration: ToastDuration
) {
if (isShown) {
return
}
if (visibility.value) {
isShown = true
Box(
modifier = Modifier.fillMaxSize().padding(bottom = 20.dp),
contentAlignment = Alignment.BottomCenter
) {
Surface(
modifier = Modifier.preferredSize(300.dp, 70.dp),
color = Color(23, 23, 23),
shape = RoundedCornerShape(4.dp)
) {
Box(contentAlignment = Alignment.Center) {
Text(
text = text,
color = Color(210, 210, 210)
)
}
DisposableEffect(Unit) {
GlobalScope.launch {
delay(duration.value.toLong())
isShown = false
visibility.value = false
}
onDispose { }
}
}
}
}
}

View File

@ -0,0 +1,25 @@
package com.shabinder.common
sealed class DownloadResult {
data class Error(val message: String, val cause: Exception? = null) : DownloadResult()
data class Progress(val progress: Int): DownloadResult()
data class Success(val byteArray: ByteArray) : DownloadResult() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Success
if (!byteArray.contentEquals(other.byteArray)) return false
return true
}
override fun hashCode(): Int {
return byteArray.contentHashCode()
}
}
}

View File

@ -24,10 +24,8 @@ kotlin {
api(Koin.test)
api(Extras.kermit)
api(Extras.jsonKlaxon)
api(Extras.youtubeDownloader)
//api(Extras.fuzzyWuzzy)
//api("com.github.willowtreeapps:fuzzywuzzy-kotlin:v0.1.1")
api(Extras.mp3agic)
}
}
androidMain {

View File

@ -1,10 +1,12 @@
package com.shabinder.common
import android.content.Context
import android.graphics.Bitmap
import android.os.Environment
import co.touchlab.kermit.Kermit
import com.shabinder.common.database.appContext
import java.io.File
import java.io.*
import java.nio.charset.StandardCharsets
actual fun openPlatform(platformID:String ,platformLink:String){
//TODO
@ -21,23 +23,3 @@ actual fun giveDonation(){
actual fun downloadTracks(list: List<TrackDetails>){
//TODO
}
actual open class Dir actual constructor(logger: Kermit) {
private val context:Context
get() = appContext
actual fun fileSeparator(): String = File.separator
actual fun imageDir(): String = context.cacheDir.absolutePath + File.separator
@Suppress("DEPRECATION")
actual fun defaultDir(): String =
Environment.getExternalStorageDirectory().toString() + File.separator +
Environment.DIRECTORY_MUSIC + File.separator +
"SpotiFlyer"+ File.separator
actual fun isPresent(path: String): Boolean = File(path).exists()
actual fun createDirectory(dirPath: String) {
}
}

View File

@ -0,0 +1,90 @@
package com.shabinder.common
import android.content.Context
import android.graphics.Bitmap
import android.os.Environment
import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File
import com.shabinder.common.database.appContext
import java.io.*
import java.nio.charset.StandardCharsets
actual open class Dir actual constructor(
private val logger: Kermit
) {
private val context: Context
get() = appContext
actual fun fileSeparator(): String = File.separator
actual fun imageCacheDir(): String = context.cacheDir.absolutePath + File.separator
@Suppress("DEPRECATION")
actual fun defaultDir(): String =
Environment.getExternalStorageDirectory().toString() + File.separator +
Environment.DIRECTORY_MUSIC + File.separator +
"SpotiFlyer"+ File.separator
actual fun isPresent(path: String): Boolean = File(path).exists()
actual fun createDirectory(dirPath: String) {
val yourAppDir = File(dirPath)
if(!yourAppDir.exists() && !yourAppDir.isDirectory)
{ // create empty directory
if (yourAppDir.mkdirs())
{logger.i{"$dirPath created"}}
else
{
logger.e{"Unable to create Dir: $dirPath!"}
}
}
else {
logger.i { "$dirPath already exists" }
}
}
actual suspend fun clearCache(){
File(imageCacheDir()).deleteRecursively()
}
actual fun cacheImage(picture: Picture) {
try {
val path = imageCacheDir() + picture.name
FileOutputStream(path).use { out ->
picture.image.compress(Bitmap.CompressFormat.JPEG, 100, out)
}
val bw =
BufferedWriter(
OutputStreamWriter(
FileOutputStream(path + cacheImagePostfix()), StandardCharsets.UTF_8
)
)
bw.write(picture.source)
bw.write("\r\n${picture.width}")
bw.write("\r\n${picture.height}")
bw.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
@Suppress("BlockingMethodInNonBlockingContext")
actual suspend fun saveFileWithMetadata(
mp3ByteArray: ByteArray,
path: String,
trackDetails: TrackDetails
) {
val file = File(path)
file.writeBytes(mp3ByteArray)
Mp3File(file)
.removeAllTags()
.setId3v1Tags(trackDetails)
.setId3v2TagsAndSaveFile(trackDetails,path)
}
}

View File

@ -0,0 +1,81 @@
package com.shabinder.common
import com.mpatric.mp3agic.ID3v1Tag
import com.mpatric.mp3agic.ID3v24Tag
import com.mpatric.mp3agic.Mp3File
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileInputStream
fun Mp3File.removeAllTags(): Mp3File {
removeId3v1Tag()
removeId3v2Tag()
removeCustomTag()
return this
}
/**
* Modifying Mp3 with MetaData!
**/
fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File {
val id3v1Tag = ID3v1Tag().apply {
artist = track.artists.joinToString(",")
title = track.title
album = track.albumName
year = track.year
comment = "Genres:${track.comment}"
}
this.id3v1Tag = id3v1Tag
return this
}
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails,filePath:String){
val id3v2Tag = ID3v24Tag().apply {
artist = track.artists.joinToString(",")
title = track.title
album = track.albumName
year = track.year
comment = "Genres:${track.comment}"
lyrics = "Gonna Implement Soon"
url = track.trackUrl
}
try{
val art = File(track.albumArtPath)
val bytesArray = ByteArray(art.length().toInt())
val fis = FileInputStream(art)
fis.read(bytesArray) //read file into bytes[]
fis.close()
id3v2Tag.setAlbumImage(bytesArray, "image/jpeg")
this.id3v2Tag = id3v2Tag
saveFile(filePath)
}catch (e: java.io.FileNotFoundException){
try {
//Image Still Not Downloaded!
//Lets Download Now and Write it into Album Art
downloadFile(track.albumArtURL).collect {
when(it){
is DownloadResult.Error -> {}//Error
is DownloadResult.Success -> {
id3v2Tag.setAlbumImage(it.byteArray, "image/jpeg")
this.id3v2Tag = id3v2Tag
saveFile(filePath)
}
is DownloadResult.Progress -> {}//Nothing for Now , no progress bar to show
}
}
}catch (e: Exception){
//log("Error", "Couldn't Write Mp3 Album Art, error: ${e.stackTrace}")
}
}
}
fun Mp3File.saveFile(filePath: String){
save(filePath.substringBeforeLast('.') + ".new.mp3")
val file = File(filePath)
file.delete()
val newFile = File((filePath.substringBeforeLast('.') + ".new.mp3"))
newFile.renameTo(file)
}

View File

@ -0,0 +1,12 @@
package com.shabinder.common
import android.graphics.Bitmap
actual data class Picture(
var source: String = "",
var name: String = "",
var image: Bitmap,
var width: Int = 0,
var height: Int = 0,
var id: Int = 0
)

View File

@ -25,7 +25,6 @@ import com.shabinder.database.Database
import io.ktor.client.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient,
@ -108,7 +107,7 @@ actual class YoutubeProvider actual constructor(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArtPath = dir.imageDir() + it.videoId() + ".jpeg",
albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent(
@ -178,7 +177,7 @@ actual class YoutubeProvider actual constructor(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageDir() + "$searchId.jpeg",
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(

View File

@ -51,7 +51,7 @@ fun isInternetAvailable(): Boolean {
}
}
}
fun createHttpClient(enableNetworkLogs: Boolean,serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient {
fun createHttpClient(enableNetworkLogs: Boolean = false,serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient {
install(JsonFeature) {
this.serializer = serializer
}

View File

@ -0,0 +1,85 @@
package com.shabinder.common
import co.touchlab.kermit.Kermit
import com.shabinder.common.utils.removeIllegalChars
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.math.roundToInt
expect open class Dir(
logger: Kermit,
) {
fun isPresent(path:String):Boolean
fun fileSeparator(): String
fun defaultDir(): String
fun imageCacheDir(): String
fun createDirectory(dirPath:String)
fun cacheImage(picture: Picture)
suspend fun clearCache()
suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, path: String, trackDetails: TrackDetails)
}
suspend fun Dir.downloadFile(url: String): Flow<DownloadResult> {
return flow {
val client = createHttpClient()
val response = client.get<HttpStatement>(url).execute()
val data = ByteArray(response.contentLength()!!.toInt())
var offset = 0
do {
val currentRead = response.content.readAvailable(data, offset, data.size)
offset += currentRead
val progress = (offset * 100f / data.size).roundToInt()
emit(DownloadResult.Progress(progress))
} while (currentRead > 0)
if (response.status.isSuccess()) {
emit(DownloadResult.Success(data))
} else {
emit(DownloadResult.Error("File not downloaded"))
}
client.close()
}
}
suspend fun downloadFile(url: String): Flow<DownloadResult> {
return flow {
val client = createHttpClient()
val response = client.get<HttpStatement>(url).execute()
val data = ByteArray(response.contentLength()!!.toInt())
var offset = 0
do {
val currentRead = response.content.readAvailable(data, offset, data.size)
offset += currentRead
val progress = (offset * 100f / data.size).roundToInt()
emit(DownloadResult.Progress(progress))
} while (currentRead > 0)
if (response.status.isSuccess()) {
emit(DownloadResult.Success(data))
} else {
emit(DownloadResult.Error("File not downloaded"))
}
client.close()
}
}
fun Dir.cacheImagePostfix():String = "info"
fun Dir.getNameURL(url: String): String {
return url.substring(url.lastIndexOf('/') + 1, url.length)
}
/*
* Call this function at startup!
* */
fun Dir.createDirectories() {
createDirectory(defaultDir())
createDirectory(imageCacheDir())
createDirectory(defaultDir() + "Tracks/")
createDirectory(defaultDir() + "Albums/")
createDirectory(defaultDir() + "Playlists/")
createDirectory(defaultDir() + "YT_Downloads/")
}
fun Dir.finalOutputDir(itemName:String ,type:String, subFolder:String,defaultDir:String,extension:String = ".mp3" ): String =
defaultDir + removeIllegalChars(type) + this.fileSeparator() +
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + this.fileSeparator()} +
removeIllegalChars(itemName) + extension

View File

@ -1,9 +1,10 @@
package com.shabinder.common
import androidx.compose.runtime.Composable
import co.touchlab.kermit.Kermit
import com.shabinder.common.utils.removeIllegalChars
expect class Picture
expect fun openPlatform(platformID:String ,platformLink:String)
expect fun shareApp()
@ -11,25 +12,3 @@ expect fun shareApp()
expect fun giveDonation()
expect fun downloadTracks(list: List<TrackDetails>)
expect open class Dir(
logger: Kermit
) {
fun isPresent(path:String):Boolean
fun fileSeparator(): String
fun defaultDir(): String
fun imageDir(): String
fun createDirectory(dirPath:String)
}
fun Dir.createDirectories() {
createDirectory(defaultDir())
createDirectory(imageDir())
createDirectory(defaultDir() + "Tracks/")
createDirectory(defaultDir() + "Albums/")
createDirectory(defaultDir() + "Playlists/")
createDirectory(defaultDir() + "YT_Downloads/")
}
fun Dir.finalOutputDir(itemName:String ,type:String, subFolder:String,defaultDir:String,extension:String = ".mp3" ): String =
defaultDir + removeIllegalChars(type) + this.fileSeparator() +
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + this.fileSeparator()} +
removeIllegalChars(itemName) + extension

View File

@ -208,7 +208,7 @@ class GaanaProvider(
title = it.track_title,
artists = it.artist.map { artist -> artist?.name.toString() },
durationSec = it.duration,
albumArtPath = dir.imageDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg",
albumArtPath = dir.imageCacheDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg",
albumName = it.album_title,
year = it.release_date,
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}",

View File

@ -231,7 +231,7 @@ class SpotifyProvider(
title = it.name.toString(),
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms/1000).toInt(),
albumArtPath = dir.imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg",
albumArtPath = dir.imageCacheDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg",
albumName = it.album?.name,
year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}",

View File

@ -1,7 +1,9 @@
package com.shabinder.common
import co.touchlab.kermit.Kermit
import java.io.File
import java.io.*
import java.nio.charset.StandardCharsets
import javax.imageio.ImageIO
actual fun openPlatform(platformID:String ,platformLink:String){
//TODO
@ -18,33 +20,3 @@ actual fun giveDonation(){
actual fun downloadTracks(list: List<TrackDetails>){
//TODO
}
actual open class Dir actual constructor(private val logger: Kermit) {
actual fun fileSeparator(): String = File.separator
actual fun imageDir(): String = System.getProperty("user.home") + ".images" + File.separator
@Suppress("DEPRECATION")
actual fun defaultDir(): String = System.getProperty("user.home") + fileSeparator() +
"SpotiFlyer" + fileSeparator()
actual fun isPresent(path: String): Boolean = File(path).exists()
actual fun createDirectory(dirPath:String){
val yourAppDir = File(dirPath)
if(!yourAppDir.exists() && !yourAppDir.isDirectory)
{ // create empty directory
if (yourAppDir.mkdirs())
{logger.i{"$dirPath created"}}
else
{
logger.e{"Unable to create Dir: $dirPath!"}
}
}
else {
logger.i { "$dirPath already exists" }
}
}
}

View File

@ -0,0 +1,80 @@
package com.shabinder.common
import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File
import java.io.*
import java.nio.charset.StandardCharsets
import javax.imageio.ImageIO
actual open class Dir actual constructor(private val logger: Kermit) {
actual fun fileSeparator(): String = File.separator
actual fun imageCacheDir(): String = System.getProperty("user.home") +
fileSeparator() + "SpotiFlyer/.images" + fileSeparator()
actual fun defaultDir(): String = System.getProperty("user.home") + fileSeparator() +
"SpotiFlyer" + fileSeparator()
actual fun isPresent(path: String): Boolean = File(path).exists()
actual fun createDirectory(dirPath:String){
val yourAppDir = File(dirPath)
if(!yourAppDir.exists() && !yourAppDir.isDirectory)
{ // create empty directory
if (yourAppDir.mkdirs())
{logger.i{"$dirPath created"}}
else
{
logger.e{"Unable to create Dir: $dirPath!"}
}
}
else {
logger.i { "$dirPath already exists" }
}
}
actual suspend fun clearCache() {
File(imageCacheDir()).deleteRecursively()
}
actual fun cacheImage(picture: Picture) {
try {
val path = imageCacheDir() + picture.name
ImageIO.write(picture.image, "jpeg", File(path))
val bw =
BufferedWriter(
OutputStreamWriter(
FileOutputStream(path + cacheImagePostfix()),
StandardCharsets.UTF_8
)
)
bw.write(picture.source)
bw.write("\r\n${picture.width}")
bw.write("\r\n${picture.height}")
bw.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
@Suppress("BlockingMethodInNonBlockingContext")
actual suspend fun saveFileWithMetadata(
mp3ByteArray: ByteArray,
path: String,
trackDetails: TrackDetails
) {
val file = File(path)
file.writeBytes(mp3ByteArray)
Mp3File(file)
.removeAllTags()
.setId3v1Tags(trackDetails)
.setId3v2TagsAndSaveFile(trackDetails,path)
}
}

View File

@ -0,0 +1,82 @@
package com.shabinder.common
import com.mpatric.mp3agic.ID3v1Tag
import com.mpatric.mp3agic.ID3v24Tag
import com.mpatric.mp3agic.Mp3File
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileInputStream
fun Mp3File.removeAllTags(): Mp3File {
if (hasId3v1Tag()) removeId3v1Tag()
if (hasId3v2Tag()) removeId3v2Tag()
if (hasCustomTag()) removeCustomTag()
return this
}
/**
* Modifying Mp3 with MetaData!
**/
fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File {
val id3v1Tag = ID3v1Tag().apply {
artist = track.artists.joinToString(",")
title = track.title
album = track.albumName
year = track.year
comment = "Genres:${track.comment}"
}
this.id3v1Tag = id3v1Tag
return this
}
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails,filePath:String){
val id3v2Tag = ID3v24Tag().apply {
artist = track.artists.joinToString(",")
title = track.title
album = track.albumName
year = track.year
comment = "Genres:${track.comment}"
lyrics = "Gonna Implement Soon"
url = track.trackUrl
}
try{
val art = File(track.albumArtPath)
val bytesArray = ByteArray(art.length().toInt())
val fis = FileInputStream(art)
fis.read(bytesArray) //read file into bytes[]
fis.close()
id3v2Tag.setAlbumImage(bytesArray, "image/jpeg")
this.id3v2Tag = id3v2Tag
saveFile(filePath)
}catch (e: java.io.FileNotFoundException){
try {
//Image Still Not Downloaded!
//Lets Download Now and Write it into Album Art
downloadFile(track.albumArtURL).collect {
when(it){
is DownloadResult.Error -> {}//Error
is DownloadResult.Success -> {
id3v2Tag.setAlbumImage(it.byteArray, "image/jpeg")
this.id3v2Tag = id3v2Tag
saveFile(filePath)
}
is DownloadResult.Progress -> {}//Nothing for Now , no progress bar to show
}
}
}catch (e: Exception){
//log("Error", "Couldn't Write Mp3 Album Art, error: ${e.stackTrace}")
}
}
}
fun Mp3File.saveFile(filePath: String){
save(filePath.substringBeforeLast('.') + ".new.mp3")
val file = File(filePath)
file.delete()
val newFile = File((filePath.substringBeforeLast('.') + ".new.mp3"))
newFile.renameTo(file)
}

View File

@ -0,0 +1,12 @@
package com.shabinder.common
import java.awt.image.BufferedImage
actual data class Picture(
var source: String = "",
var name: String = "",
var image: BufferedImage,
var width: Int = 0,
var height: Int = 0,
var id: Int = 0
)

View File

@ -108,7 +108,7 @@ actual class YoutubeProvider actual constructor(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArtPath = dir.imageDir() + it.videoId() + ".jpeg",
albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent(
@ -178,7 +178,7 @@ actual class YoutubeProvider actual constructor(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageDir() + "$searchId.jpeg",
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(