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.foundation.layout.preferredWidth
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.core.net.toUri import androidx.core.net.toUri
import com.shabinder.common.database.appContext
import dev.chrisbanes.accompanist.coil.CoilImage import dev.chrisbanes.accompanist.coil.CoilImage
@Composable @Composable
@ -29,3 +31,16 @@ actual fun ImageLoad(
modifier = modifier 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

@ -11,4 +11,6 @@ expect fun ImageLoad(
loadingResource: ImageBitmap? = null, loadingResource: ImageBitmap? = null,
errorResource: ImageBitmap? = null, errorResource: ImageBitmap? = null,
modifier: Modifier = Modifier 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(Koin.test)
api(Extras.kermit) api(Extras.kermit)
api(Extras.jsonKlaxon)
api(Extras.youtubeDownloader) api(Extras.youtubeDownloader)
//api(Extras.fuzzyWuzzy) api(Extras.mp3agic)
//api("com.github.willowtreeapps:fuzzywuzzy-kotlin:v0.1.1")
} }
} }
androidMain { androidMain {

View File

@ -1,10 +1,12 @@
package com.shabinder.common package com.shabinder.common
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.os.Environment import android.os.Environment
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.appContext 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){ actual fun openPlatform(platformID:String ,platformLink:String){
//TODO //TODO
@ -20,24 +22,4 @@ actual fun giveDonation(){
actual fun downloadTracks(list: List<TrackDetails>){ actual fun downloadTracks(list: List<TrackDetails>){
//TODO //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 io.ktor.client.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
actual class YoutubeProvider actual constructor( actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient, private val httpClient: HttpClient,
@ -108,7 +107,7 @@ actual class YoutubeProvider actual constructor(
title = it.title(), title = it.title(),
artists = listOf(it.author().toString()), artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(), durationSec = it.lengthSeconds(),
albumArtPath = dir.imageDir() + it.videoId() + ".jpeg", albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube, source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg", albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent( downloaded = if (dir.isPresent(
@ -178,7 +177,7 @@ actual class YoutubeProvider actual constructor(
title = name, title = name,
artists = listOf(detail?.author().toString()), artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0, durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageDir() + "$searchId.jpeg", albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube, source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent( 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) { install(JsonFeature) {
this.serializer = serializer 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,35 +1,14 @@
package com.shabinder.common package com.shabinder.common
import androidx.compose.runtime.Composable
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.utils.removeIllegalChars import com.shabinder.common.utils.removeIllegalChars
expect class Picture
expect fun openPlatform(platformID:String ,platformLink:String) expect fun openPlatform(platformID:String ,platformLink:String)
expect fun shareApp() expect fun shareApp()
expect fun giveDonation() expect fun giveDonation()
expect fun downloadTracks(list: List<TrackDetails>) 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, 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 = dir.imageDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg", albumArtPath = dir.imageCacheDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg",
albumName = it.album_title, albumName = it.album_title,
year = it.release_date, year = it.release_date,
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}", 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(), title = it.name.toString(),
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(), artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms/1000).toInt(), 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, 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

@ -1,7 +1,9 @@
package com.shabinder.common package com.shabinder.common
import co.touchlab.kermit.Kermit 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){ actual fun openPlatform(platformID:String ,platformLink:String){
//TODO //TODO
@ -17,34 +19,4 @@ actual fun giveDonation(){
actual fun downloadTracks(list: List<TrackDetails>){ actual fun downloadTracks(list: List<TrackDetails>){
//TODO //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(), title = it.title(),
artists = listOf(it.author().toString()), artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(), durationSec = it.lengthSeconds(),
albumArtPath = dir.imageDir() + it.videoId() + ".jpeg", albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube, source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg", albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent( downloaded = if (dir.isPresent(
@ -178,7 +178,7 @@ actual class YoutubeProvider actual constructor(
title = name, title = name,
artists = listOf(detail?.author().toString()), artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0, durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageDir() + "$searchId.jpeg", albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube, source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent( downloaded = if (dir.isPresent(