Migrating to youtube-api-dl

This commit is contained in:
shabinder 2021-04-16 22:43:45 +05:30
parent c9696fa4aa
commit e3c3a6cb6c
14 changed files with 186 additions and 447 deletions

View File

@ -25,6 +25,7 @@ allprojects {
google() google()
jcenter() jcenter()
mavenCentral() mavenCentral()
mavenLocal()
maven(url = "https://jitpack.io") maven(url = "https://jitpack.io")
maven(url = "https://dl.bintray.com/ekito/koin") maven(url = "https://dl.bintray.com/ekito/koin")
maven(url = "https://kotlin.bintray.com/kotlinx/") maven(url = "https://kotlin.bintray.com/kotlinx/")

View File

@ -136,7 +136,8 @@ object Ktor {
} }
object Extras { object Extras {
const val youtubeDownloader = "com.github.sealedtx:java-youtube-downloader:2.5.1" //const val youtubeDownloader = "com.github.sealedtx:java-youtube-downloader:2.5.1"
const val youtubeDownloader = "com.shabinder.downloader:youtube-api-dl:0.1-SNAPSHOT" //Local Maven
const val fuzzyWuzzy = "me.xdrop:fuzzywuzzy:1.3.1" const val fuzzyWuzzy = "me.xdrop:fuzzywuzzy:1.3.1"
const val mp3agic = "com.mpatric:mp3agic:0.9.1" const val mp3agic = "com.mpatric:mp3agic:0.9.1"
const val kermit = "co.touchlab:kermit:${Versions.kermit}" const val kermit = "co.touchlab:kermit:${Versions.kermit}"

View File

@ -26,20 +26,17 @@ import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TopAppBar import androidx.compose.material.TopAppBar
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -47,7 +44,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.jetbrains.Children import com.arkivanov.decompose.extensions.compose.jetbrains.Children

View File

@ -27,7 +27,7 @@ kotlin {
dependencies { dependencies {
api("dev.icerock.moko:parcelize:0.6.0") api("dev.icerock.moko:parcelize:0.6.0")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt")
} }
} }
} }

View File

@ -37,6 +37,7 @@ kotlin {
implementation(Ktor.clientLogging) implementation(Ktor.clientLogging)
implementation(Ktor.clientJson) implementation(Ktor.clientJson)
implementation(Ktor.auth) implementation(Ktor.auth)
api(Extras.youtubeDownloader)
// koin // koin
api(Koin.core) api(Koin.core)
api(Koin.test) api(Koin.test)
@ -50,7 +51,6 @@ kotlin {
implementation(Ktor.clientAndroid) implementation(Ktor.clientAndroid)
implementation(Extras.Android.fetch) implementation(Extras.Android.fetch)
implementation(Extras.Android.razorpay) implementation(Extras.Android.razorpay)
api(Extras.youtubeDownloader)
api(Extras.mp3agic) api(Extras.mp3agic)
// api(files("$rootDir/libs/mobile-ffmpeg.aar")) // api(files("$rootDir/libs/mobile-ffmpeg.aar"))
} }
@ -60,7 +60,6 @@ kotlin {
implementation(compose.materialIconsExtended) implementation(compose.materialIconsExtended)
implementation(Ktor.clientApache) implementation(Ktor.clientApache)
implementation(Ktor.slf4j) implementation(Ktor.slf4j)
api(Extras.youtubeDownloader)
api(Extras.mp3agic) api(Extras.mp3agic)
} }
} }

View File

@ -1,188 +0,0 @@
/*
* Copyright (c) 2021 Shabinder Singh
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import io.ktor.client.HttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient,
private val logger: Kermit,
private val dir: Dir,
) {
val ytDownloader: YoutubeDownloader = YoutubeDownloader()
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
private val sampleDomain1 = "music.youtube.com"
private val sampleDomain2 = "youtube.com"
private val sampleDomain3 = "youtu.be"
actual suspend fun query(fullLink: String): PlatformQueryResult? = withContext(Dispatchers.IO) {
val link = fullLink.removePrefix("https://").removePrefix("http://")
if (link.contains("playlist", true) || link.contains("list", true)) {
// Given Link is of a Playlist
logger.i { link }
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&").substringBefore("?")
getYTPlaylist(
playlistId
)
} else { // Given Link is of a Video
var searchId = "error"
when {
link.contains(sampleDomain1, true) -> { // Youtube Music
searchId = link.substringAfterLast("/", "error").substringBefore("&").substringAfterLast("=")
}
link.contains(sampleDomain2, true) -> { // Standard Youtube Link
searchId = link.substringAfterLast("=", "error").substringBefore("&")
}
link.contains(sampleDomain3, true) -> { // Shortened Youtube Link
searchId = link.substringAfterLast("/", "error").substringBefore("&")
}
}
if (searchId != "error") {
getYTTrack(
searchId
)
} else {
logger.d { "Your Youtube Link is not of a Video!!" }
null
}
}
}
private suspend fun getYTPlaylist(
searchId: String
): PlatformQueryResult? = withContext(Dispatchers.IO) {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
)
result.apply {
try {
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details()
val name = playlistDetails.title()
subFolder = removeIllegalChars(name)
val videos = playlist.videos()
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId()
}/hqdefault.jpg"
title = name
trackList = videos.map {
TrackDetails(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = it.title(),
type = folderType,
subFolder = subFolder,
dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = it.videoId()
)
}
} catch (e: Exception) {
e.printStackTrace()
logger.d { "An Error Occurred While Processing!" }
}
}
if (result.title.isNotBlank()) result else null
}
@Suppress("DefaultLocale")
private suspend fun getYTTrack(
searchId: String,
): PlatformQueryResult? = withContext(Dispatchers.IO) {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
).apply {
try {
logger.i { searchId }
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(), "", true)
?: detail?.title() ?: ""
// logger.i{ detail.toString() }
trackList = listOf(
TrackDetails(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder,
defaultDir = dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = searchId
)
)
title = name
} catch (e: Exception) {
e.printStackTrace()
logger.e { "An Error Occurred While Processing!,$searchId" }
}
}
if (result.title.isNotBlank()) result else null
}
}

View File

@ -38,14 +38,14 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.R import com.shabinder.common.di.R
import com.shabinder.common.di.getData import com.shabinder.common.di.getData
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader
import com.shabinder.downloader.models.formats.Format
import com.tonyodev.fetch2.Download import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Error import com.tonyodev.fetch2.Error
import com.tonyodev.fetch2.Fetch import com.tonyodev.fetch2.Fetch
@ -198,7 +198,7 @@ class ForegroundService : Service(), CoroutineScope {
val url = fetcher.youtubeMp3.getMp3DownloadLink(videoID) val url = fetcher.youtubeMp3.getMp3DownloadLink(videoID)
if (url == null) { if (url == null) {
val audioData: Format = ytDownloader.getVideo(videoID).getData() ?: throw Exception("Java YT Dependency Error") val audioData: Format = ytDownloader.getVideo(videoID).getData() ?: throw Exception("Java YT Dependency Error")
val ytUrl: String = audioData.url() val ytUrl = audioData.url!! //We Will catch NPE
enqueueDownload(ytUrl, track) enqueueDownload(ytUrl, track)
} else enqueueDownload(url, track) } else enqueueDownload(url, track)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -17,13 +17,182 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import com.shabinder.downloader.YoutubeDownloader
import com.shabinder.downloader.models.YoutubeVideo
import com.shabinder.downloader.models.formats.Format
import com.shabinder.downloader.models.quality.AudioQuality
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
expect class YoutubeProvider( class YoutubeProvider(
httpClient: HttpClient, private val httpClient: HttpClient,
logger: Kermit, private val logger: Kermit,
dir: Dir private val dir: Dir,
) { ) {
suspend fun query(fullLink: String): PlatformQueryResult? val ytDownloader: YoutubeDownloader = YoutubeDownloader()
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
private val sampleDomain1 = "music.youtube.com"
private val sampleDomain2 = "youtube.com"
private val sampleDomain3 = "youtu.be"
suspend fun query(fullLink: String): PlatformQueryResult? {
val link = fullLink.removePrefix("https://").removePrefix("http://")
if (link.contains("playlist", true) || link.contains("list", true)) {
// Given Link is of a Playlist
logger.i { link }
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&").substringBefore("?")
return getYTPlaylist(
playlistId
)
} else { // Given Link is of a Video
var searchId = "error"
when {
link.contains(sampleDomain1, true) -> { // Youtube Music
searchId = link.substringAfterLast("/", "error").substringBefore("&").substringAfterLast("=")
}
link.contains(sampleDomain2, true) -> { // Standard Youtube Link
searchId = link.substringAfterLast("=", "error").substringBefore("&")
}
link.contains(sampleDomain3, true) -> { // Shortened Youtube Link
searchId = link.substringAfterLast("/", "error").substringBefore("&")
}
}
return if (searchId != "error") {
getYTTrack(
searchId
)
} else {
logger.d { "Your Youtube Link is not of a Video!!" }
null
}
}
}
private suspend fun getYTPlaylist(
searchId: String
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
)
result.apply {
try {
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details
val name = playlistDetails.title
subFolder = removeIllegalChars(name)
val videos = playlist.videos
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId
}/hqdefault.jpg"
title = name
trackList = videos.map {
TrackDetails(
title = it.title ?: "N/A",
artists = listOf(it.author ?: "N/A"),
durationSec = it.lengthSeconds,
albumArtPath = dir.imageCacheDir() + it.videoId + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = it.title ?: "N/A",
type = folderType,
subFolder = subFolder,
dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(it.title ?: "N/A", folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = it.videoId
)
}
} catch (e: Exception) {
e.printStackTrace()
logger.d { "An Error Occurred While Processing!" }
}
}
return if (result.title.isNotBlank()) result
else null
}
@Suppress("DefaultLocale")
private suspend fun getYTTrack(
searchId: String,
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
).apply {
try {
logger.i { searchId }
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video.videoDetails
val name = detail.title?.replace(detail.author?.toUpperCase() ?: "", "", true)
?: detail.title ?: ""
// logger.i{ detail.toString() }
trackList = listOf(
TrackDetails(
title = name,
artists = listOf(detail.author ?: "N/A"),
durationSec = detail.lengthSeconds,
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder,
defaultDir = dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = searchId
)
)
title = name
} catch (e: Exception) {
e.printStackTrace()
logger.e { "An Error Occurred While Processing!,$searchId" }
}
}
return if (result.title.isNotBlank()) result
else null
}
}
fun YoutubeVideo.getData(): Format? {
return getAudioWithQuality(AudioQuality.high).getOrNull(0)
?: getAudioWithQuality(AudioQuality.medium).getOrNull(0)
?: getAudioWithQuality(AudioQuality.low).getOrNull(0)
} }

View File

@ -16,15 +16,12 @@
package com.shabinder.common.di 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.di.utils.ParallelExecutor import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader
import io.ktor.client.request.head import io.ktor.client.request.head
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -115,7 +112,7 @@ suspend fun downloadTrack(
val audioData = ytDownloader.getVideo(videoID).getData() val audioData = ytDownloader.getVideo(videoID).getData()
audioData?.let { format -> audioData?.let { format ->
val url: String = format.url() val url = format.url ?: return
downloadFile(url).collect { downloadFile(url).collect {
when (it) { when (it) {
is DownloadResult.Error -> { is DownloadResult.Error -> {
@ -147,18 +144,3 @@ suspend fun downloadTrack(
e.printStackTrace() 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
}
}
}
}

View File

@ -1,189 +0,0 @@
/*
* Copyright (c) 2021 Shabinder Singh
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import io.ktor.client.HttpClient
actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient,
private val logger: Kermit,
private val dir: Dir,
) {
private val ytDownloader: YoutubeDownloader = YoutubeDownloader()
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
private val sampleDomain1 = "music.youtube.com"
private val sampleDomain2 = "youtube.com"
private val sampleDomain3 = "youtu.be"
actual suspend fun query(fullLink: String): PlatformQueryResult? {
val link = fullLink.removePrefix("https://").removePrefix("http://")
if (link.contains("playlist", true) || link.contains("list", true)) {
// Given Link is of a Playlist
logger.i { link }
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&").substringBefore("?")
return getYTPlaylist(
playlistId
)
} else { // Given Link is of a Video
var searchId = "error"
when {
link.contains(sampleDomain1, true) -> { // Youtube Music
searchId = link.substringAfterLast("/", "error").substringBefore("&").substringAfterLast("=")
}
link.contains(sampleDomain2, true) -> { // Standard Youtube Link
searchId = link.substringAfterLast("=", "error").substringBefore("&")
}
link.contains(sampleDomain3, true) -> { // Shortened Youtube Link
searchId = link.substringAfterLast("/", "error").substringBefore("&")
}
}
return if (searchId != "error") {
getYTTrack(
searchId
)
} else {
logger.d { "Your Youtube Link is not of a Video!!" }
null
}
}
}
private suspend fun getYTPlaylist(
searchId: String
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
)
result.apply {
try {
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details()
val name = playlistDetails.title()
subFolder = removeIllegalChars(name)
val videos = playlist.videos()
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId()
}/hqdefault.jpg"
title = name
trackList = videos.map {
TrackDetails(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = it.title(),
type = folderType,
subFolder = subFolder,
dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = it.videoId()
)
}
} catch (e: Exception) {
e.printStackTrace()
logger.d { "An Error Occurred While Processing!" }
}
}
return if (result.title.isNotBlank()) result
else null
}
@Suppress("DefaultLocale")
private suspend fun getYTTrack(
searchId: String,
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
).apply {
try {
logger.i { searchId }
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(), "", true)
?: detail?.title() ?: ""
// logger.i{ detail.toString() }
trackList = listOf(
TrackDetails(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder,
defaultDir = dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = searchId
)
)
title = name
} catch (e: Exception) {
e.printStackTrace()
logger.e { "An Error Occurred While Processing!,$searchId" }
}
}
return if (result.title.isNotBlank()) result
else null
}
}

View File

@ -1,31 +0,0 @@
/*
* * Copyright (c) 2021 Shabinder Singh
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.shabinder.common.models.PlatformQueryResult
import io.ktor.client.HttpClient
actual class YoutubeProvider actual constructor(
httpClient: HttpClient,
logger: Kermit,
dir: Dir
) {
actual suspend fun query(fullLink: String): PlatformQueryResult? {
return null // TODO
}
}

View File

@ -14,7 +14,6 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import co.touchlab.kermit.Kermit
import com.arkivanov.decompose.DefaultComponentContext import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.decompose.lifecycle.LifecycleRegistry import com.arkivanov.decompose.lifecycle.LifecycleRegistry
import com.arkivanov.decompose.lifecycle.destroy import com.arkivanov.decompose.lifecycle.destroy

View File

@ -32,7 +32,7 @@ class RootR(props: Props<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRo
initialState = State(routerState = props.model.routerState.value) initialState = State(routerState = props.model.routerState.value)
) { ) {
private val component: Child private val component: Child
get() = model.routerState.value.activeChild.component get() = model.routerState.value.activeChild.instance
private val callBacks get() = model.callBacks private val callBacks get() = model.callBacks