mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-25 05:57:54 +01:00
Youtube Music Fetching Fixed for Native
This commit is contained in:
parent
7a3a299aa2
commit
c278d6379a
@ -264,6 +264,8 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeMp3Tags(trackDetails: TrackDetails) {/*IMPLEMENTED*/}
|
||||
|
||||
override val isInternetAvailable get() = internetAvailability.value ?: true
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ interface Actions {
|
||||
|
||||
// Open / Redirect to another Platform
|
||||
fun openPlatform(packageID: String, platformLink: String)
|
||||
fun writeMp3Tags(trackDetails: TrackDetails)
|
||||
}
|
||||
|
||||
|
||||
@ -49,5 +50,7 @@ private fun stubActions() = object :Actions{
|
||||
override fun giveDonation() {}
|
||||
override fun shareApp() {}
|
||||
override fun openPlatform(packageID: String, platformLink: String) {}
|
||||
override fun writeMp3Tags(trackDetails: TrackDetails) {}
|
||||
|
||||
override val isInternetAvailable: Boolean = true
|
||||
}
|
@ -3,6 +3,7 @@ package com.shabinder.common.models
|
||||
import kotlin.native.concurrent.AtomicReference
|
||||
|
||||
actual interface PlatformActions {}
|
||||
|
||||
actual val StubPlatformActions = object: PlatformActions {}
|
||||
|
||||
actual typealias NativeAtomicReference<T> = AtomicReference<T>
|
@ -72,6 +72,9 @@ val kotlinxSerializer = KotlinxSerializer(
|
||||
|
||||
fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient {
|
||||
// https://github.com/Kotlin/kotlinx.serialization/issues/1450
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer()
|
||||
}
|
||||
/*install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(
|
||||
Json {
|
||||
|
@ -30,6 +30,7 @@ class YoutubeMp3(
|
||||
private val dir: Dir,
|
||||
) : Yt1sMp3 {
|
||||
suspend fun getMp3DownloadLink(videoID: String): String? = try {
|
||||
logger.i { "Youtube MP3 Link Fetching!" }
|
||||
getLinkFromYt1sMp3(videoID)?.let {
|
||||
logger.i { "Download Link: $it" }
|
||||
if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/)
|
||||
|
@ -29,6 +29,7 @@ import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
@ -65,7 +66,7 @@ class YoutubeMusic constructor(
|
||||
val youtubeTracks = mutableListOf<YoutubeTrack>()
|
||||
|
||||
val responseObj = Json.parseToJsonElement(getYoutubeMusicResponse(query))
|
||||
|
||||
logger.i { "Youtube Music Response Recieved" }
|
||||
val contentBlocks = responseObj.jsonObject["contents"]
|
||||
?.jsonObject?.get("sectionListRenderer")
|
||||
?.jsonObject?.get("contents")?.jsonArray
|
||||
@ -284,7 +285,8 @@ class YoutubeMusic constructor(
|
||||
}
|
||||
|
||||
private suspend fun getYoutubeMusicResponse(query: String): String {
|
||||
return httpClient.postData("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey") {
|
||||
logger.i { "Fetching Youtube Music Response" }
|
||||
return httpClient.post("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey") {
|
||||
contentType(ContentType.Application.Json)
|
||||
headers {
|
||||
append("referer", "https://music.youtube.com/search")
|
||||
|
@ -49,6 +49,7 @@ suspend inline fun <reified T: Any> HttpClient.postData(
|
||||
): T {
|
||||
val response = post<HttpResponse> {
|
||||
url.takeFrom(urlString)
|
||||
header(HttpHeaders.ContentType, ContentType.Application.Json)
|
||||
block()
|
||||
}
|
||||
val jsonBody = response.readText()
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.shabinder.common.di
|
||||
|
||||
import com.shabinder.common.di.providers.YoutubeMp3
|
||||
import com.shabinder.common.di.utils.ParallelExecutor
|
||||
import com.shabinder.common.models.AllPlatforms
|
||||
import com.shabinder.common.models.DownloadResult
|
||||
@ -46,7 +47,7 @@ actual suspend fun downloadTracks(
|
||||
list.forEach {
|
||||
DownloadScope.execute { // Send Download to Pool.
|
||||
if (!it.videoID.isNullOrBlank()) { // Video ID already known!
|
||||
downloadTrack(it.videoID!!, it, dir::saveFileWithMetadata)
|
||||
downloadTrack(it.videoID!!, it, dir::saveFileWithMetadata, fetcher.youtubeMp3)
|
||||
} else {
|
||||
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
||||
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery, it)
|
||||
@ -57,7 +58,7 @@ actual suspend fun downloadTracks(
|
||||
) { hashMapOf() }.apply { set(it.title, DownloadStatus.Failed) }
|
||||
)
|
||||
} else { // Found Youtube Video ID
|
||||
downloadTrack(videoId, it, dir::saveFileWithMetadata)
|
||||
downloadTrack(videoId, it, dir::saveFileWithMetadata,fetcher.youtubeMp3)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,37 +70,38 @@ private val ytDownloader = YoutubeDownloader()
|
||||
suspend fun downloadTrack(
|
||||
videoID: String,
|
||||
trackDetails: TrackDetails,
|
||||
saveFileWithMetaData: suspend (mp3ByteArray: ByteArray, trackDetails: TrackDetails,postProcess:(TrackDetails)->Unit) -> Unit
|
||||
saveFileWithMetaData: suspend (mp3ByteArray: ByteArray, trackDetails: TrackDetails, postProcess: (TrackDetails) -> Unit) -> Unit,
|
||||
youtubeMp3: YoutubeMp3
|
||||
) {
|
||||
try {
|
||||
val audioData = ytDownloader.getVideo(videoID).getData()
|
||||
var link = youtubeMp3.getMp3DownloadLink(videoID)
|
||||
|
||||
audioData?.let { format ->
|
||||
val url = format.url ?: return
|
||||
downloadFile(url).collect {
|
||||
when (it) {
|
||||
is DownloadResult.Error -> {
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) }
|
||||
)
|
||||
}
|
||||
is DownloadResult.Progress -> {
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloading(it.progress)) }
|
||||
)
|
||||
}
|
||||
is DownloadResult.Success -> { // Todo clear map
|
||||
saveFileWithMetaData(it.byteArray, trackDetails){}
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) }
|
||||
)
|
||||
}
|
||||
if (link == null) {
|
||||
link = ytDownloader.getVideo(videoID).getData()?.url ?: return
|
||||
}
|
||||
downloadFile(link).collect {
|
||||
when (it) {
|
||||
is DownloadResult.Error -> {
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) }
|
||||
)
|
||||
}
|
||||
is DownloadResult.Progress -> {
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloading(it.progress)) }
|
||||
)
|
||||
}
|
||||
is DownloadResult.Success -> { // Todo clear map
|
||||
saveFileWithMetaData(it.byteArray, trackDetails){}
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,15 @@
|
||||
package com.shabinder.common.di
|
||||
|
||||
import com.shabinder.common.di.providers.YoutubeMp3
|
||||
import com.shabinder.common.di.utils.ParallelExecutor
|
||||
import com.shabinder.common.models.AllPlatforms
|
||||
import com.shabinder.common.models.DownloadResult
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.methods
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
||||
@SharedImmutable
|
||||
actual val dispatcherIO = Dispatchers.Default
|
||||
@ -10,10 +17,85 @@ actual val dispatcherIO = Dispatchers.Default
|
||||
@SharedImmutable
|
||||
actual val currentPlatform: AllPlatforms = AllPlatforms.Native
|
||||
|
||||
@SharedImmutable
|
||||
val Downloader = ParallelExecutor(dispatcherIO)
|
||||
|
||||
actual suspend fun downloadTracks(
|
||||
list: List<TrackDetails>,
|
||||
fetcher: FetchPlatformQueryResult,
|
||||
dir: Dir
|
||||
) {
|
||||
// TODO
|
||||
}
|
||||
dir.logger.i { "Downloading ${list.size} Tracks" }
|
||||
for (track in list) {
|
||||
Downloader.execute {
|
||||
if (!track.videoID.isNullOrBlank()) { // Video ID already known!
|
||||
dir.logger.i { "VideoID: ${track.title} -> ${track.videoID}" }
|
||||
downloadTrack(track.videoID!!, track, dir::saveFileWithMetadata,fetcher)
|
||||
} else {
|
||||
val searchQuery = "${track.title} - ${track.artists.joinToString(",")}"
|
||||
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery, track)
|
||||
dir.logger.i { "VideoID: ${track.title} -> $videoId" }
|
||||
if (videoId.isNullOrBlank()) {
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(track.title, DownloadStatus.Failed) }
|
||||
)
|
||||
} else { // Found Youtube Video ID
|
||||
downloadTrack(videoId, track, dir::saveFileWithMetadata,fetcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SharedImmutable
|
||||
val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1)
|
||||
|
||||
suspend fun downloadTrack(
|
||||
videoID: String,
|
||||
trackDetails: TrackDetails,
|
||||
saveFileWithMetaData: suspend (mp3ByteArray: ByteArray, trackDetails: TrackDetails, postProcess: (TrackDetails) -> Unit) -> Unit,
|
||||
fetcher: FetchPlatformQueryResult
|
||||
) {
|
||||
try {
|
||||
var link = fetcher.youtubeMp3.getMp3DownloadLink(videoID)
|
||||
|
||||
fetcher.dir.logger.i { "LINK: $videoID -> $link" }
|
||||
if (link == null) {
|
||||
link = fetcher.youtubeProvider.ytDownloader.getVideo(videoID).getData()?.url ?: return
|
||||
}
|
||||
fetcher.dir.logger.i { "LINK: $videoID -> $link" }
|
||||
downloadFile(link).collect {
|
||||
fetcher.dir.logger.d { it.toString() }
|
||||
when (it) {
|
||||
is DownloadResult.Error -> {
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) }
|
||||
)
|
||||
}
|
||||
is DownloadResult.Progress -> {
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply {
|
||||
set(trackDetails.title, DownloadStatus.Downloading(it.progress))
|
||||
}
|
||||
)
|
||||
}
|
||||
is DownloadResult.Success -> { // Todo clear map
|
||||
saveFileWithMetaData(it.byteArray, trackDetails, methods.value::writeMp3Tags)
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,6 @@ object IOSDeps: KoinComponent {
|
||||
val dir: Dir by inject() // = get()
|
||||
val fetchPlatformQueryResult: FetchPlatformQueryResult by inject() // get()
|
||||
val database get() = dir.db
|
||||
val sharedFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
|
||||
val sharedFlow = DownloadProgressFlow
|
||||
val defaultDispatcher = dispatcherDefault
|
||||
}
|
@ -22,7 +22,7 @@ import platform.UIKit.UIImage
|
||||
import platform.UIKit.UIImageJPEGRepresentation
|
||||
|
||||
actual class Dir actual constructor(
|
||||
private val logger: Kermit,
|
||||
val logger: Kermit,
|
||||
private val spotiFlyerDatabase: SpotiFlyerDatabase,
|
||||
) {
|
||||
|
||||
@ -134,16 +134,36 @@ actual class Dir actual constructor(
|
||||
trackDetails: TrackDetails,
|
||||
postProcess:(track: TrackDetails)->Unit
|
||||
) : Unit = withContext(dispatcherIO) {
|
||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||
".mp3" -> {
|
||||
postProcess(trackDetails)
|
||||
/*val file = TLAudio(trackDetails.outputFilePath)
|
||||
file.addTagsAndSave(
|
||||
try {
|
||||
if (mp3ByteArray.isNotEmpty()) {
|
||||
mp3ByteArray.toNSData().writeToFile(
|
||||
trackDetails.outputFilePath,
|
||||
true
|
||||
)
|
||||
}
|
||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||
".mp3" -> {
|
||||
if(!isPresent(trackDetails.albumArtPath)) {
|
||||
val imageData = downloadByteArray(
|
||||
trackDetails.albumArtURL
|
||||
)?.toNSData()
|
||||
if (imageData != null) {
|
||||
UIImage.imageWithData(imageData)?.also {
|
||||
cacheImage(it, trackDetails.albumArtPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
postProcess(trackDetails)
|
||||
/*val file = TLAudio(trackDetails.outputFilePath)
|
||||
file.addTagsAndSave(
|
||||
trackDetails,
|
||||
this::loadCachedImage,
|
||||
this::addToLibrary
|
||||
)*/
|
||||
}
|
||||
}
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ import com.shabinder.common.di.isInternetAccessible
|
||||
import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.AllPlatforms
|
||||
import com.shabinder.common.models.PlatformActions
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.common.uikit.SpotiFlyerColors
|
||||
import com.shabinder.common.uikit.SpotiFlyerRootContent
|
||||
@ -92,14 +93,12 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
|
||||
|
||||
override fun openPlatform(packageID: String, platformLink: String) {}
|
||||
|
||||
override val dispatcherIO = Dispatchers.IO
|
||||
override fun writeMp3Tags(trackDetails: TrackDetails) {/*IMPLEMENTED*/}
|
||||
|
||||
override val isInternetAvailable: Boolean
|
||||
get() = runBlocking {
|
||||
isInternetAccessible()
|
||||
}
|
||||
|
||||
override val currentPlatform = AllPlatforms.Jvm
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -25,6 +25,7 @@ import com.shabinder.common.di.DownloadProgressFlow
|
||||
import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.AllPlatforms
|
||||
import com.shabinder.common.models.PlatformActions
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.database.Database
|
||||
import extras.renderableChild
|
||||
@ -77,9 +78,9 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
|
||||
|
||||
override fun openPlatform(packageID: String, platformLink: String) {}
|
||||
|
||||
override val dispatcherIO = Dispatchers.Default
|
||||
override fun writeMp3Tags(trackDetails: TrackDetails) {/*IMPLEMENTED*/}
|
||||
|
||||
override val isInternetAvailable: Boolean = true
|
||||
override val currentPlatform = AllPlatforms.Js
|
||||
}
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user