mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-24 18:04:33 +01:00
YT Image URL Schema Change from HI_RES->HQ-Default
This commit is contained in:
parent
c0e3a35898
commit
a5793cc72c
@ -6,6 +6,7 @@
|
||||
<w>emoji</w>
|
||||
<w>ffmpeg</w>
|
||||
<w>flyer</w>
|
||||
<w>hqdefault</w>
|
||||
<w>insta</w>
|
||||
<w>instagram</w>
|
||||
<w>jetbrains</w>
|
||||
|
@ -48,8 +48,8 @@ object YTDownloadHelper {
|
||||
|
||||
val downloadObject = DownloadObject(
|
||||
trackDetails = it,
|
||||
ytVideoId = "https://i.ytimg.com/vi/${it.albumArt.absolutePath.substringAfterLast("/")
|
||||
.substringBeforeLast(".")}/maxresdefault.jpg",
|
||||
ytVideoId = it.albumArt.absolutePath.substringAfterLast("/")
|
||||
.substringBeforeLast("."),
|
||||
outputFile = outputFile
|
||||
)
|
||||
|
||||
|
@ -44,7 +44,7 @@ class DownloadRecordAdapter: ListAdapter<DownloadRecord,DownloadRecordAdapter.Vi
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
adapterScope.launch {
|
||||
bindImage(holder.binding.coverUrl,item.coverUrl)
|
||||
bindImage(holder.binding.coverUrl,item.coverUrl,null)
|
||||
}
|
||||
holder.binding.itemName.text = item.name
|
||||
holder.binding.totalItems.text = "Tracks: ${item.totalFiles}"
|
||||
|
@ -20,19 +20,22 @@ package com.shabinder.spotiflyer.recyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.databinding.TrackListItemBinding
|
||||
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
|
||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||
import com.shabinder.spotiflyer.models.Source
|
||||
import com.shabinder.spotiflyer.models.TrackDetails
|
||||
import com.shabinder.spotiflyer.ui.youtube.YoutubeViewModel
|
||||
import com.shabinder.spotiflyer.utils.Provider
|
||||
import com.shabinder.spotiflyer.utils.bindImage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import com.shabinder.spotiflyer.utils.rotateAnim
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class YoutubeTrackListAdapter: ListAdapter<TrackDetails,SpotifyTrackListAdapter.ViewHolder>(YouTubeTrackDiffCallback()) {
|
||||
|
||||
private val adapterScope = CoroutineScope(Dispatchers.Default)
|
||||
class YoutubeTrackListAdapter(private val youtubeViewModel :YoutubeViewModel): ListAdapter<TrackDetails,SpotifyTrackListAdapter.ViewHolder>(YouTubeTrackDiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
@ -48,24 +51,50 @@ class YoutubeTrackListAdapter: ListAdapter<TrackDetails,SpotifyTrackListAdapter.
|
||||
val item = getItem(position)
|
||||
if(itemCount == 1){
|
||||
holder.binding.imageUrl.visibility = View.GONE}else{
|
||||
adapterScope.launch {
|
||||
youtubeViewModel.uiScope.launch {
|
||||
bindImage(holder.binding.imageUrl,
|
||||
"https://i.ytimg.com/vi/${item.albumArt.absolutePath.substringAfterLast("/")
|
||||
.substringBeforeLast(".")}/maxresdefault.jpg"
|
||||
.substringBeforeLast(".")}/hqdefault.jpg"
|
||||
,
|
||||
Source.YouTube
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when (item.downloaded) {
|
||||
DownloadStatus.Downloaded -> {
|
||||
holder.binding.btnDownload.setImageResource(R.drawable.ic_tick)
|
||||
holder.binding.btnDownload.clearAnimation()
|
||||
}
|
||||
DownloadStatus.Downloading -> {
|
||||
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
|
||||
rotateAnim(holder.binding.btnDownload)
|
||||
}
|
||||
DownloadStatus.NotDownloaded -> {
|
||||
holder.binding.btnDownload.setImageResource(R.drawable.ic_arrow)
|
||||
holder.binding.btnDownload.clearAnimation()
|
||||
holder.binding.btnDownload.setOnClickListener{
|
||||
Toast.makeText(Provider.activity,"Processing!", Toast.LENGTH_SHORT).show()
|
||||
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
|
||||
rotateAnim(it)
|
||||
item.downloaded = DownloadStatus.Downloading
|
||||
youtubeViewModel.uiScope.launch {
|
||||
val itemList = mutableListOf<TrackDetails>()
|
||||
itemList.add(item)
|
||||
YTDownloadHelper.downloadYTTracks(
|
||||
youtubeViewModel.folderType,
|
||||
youtubeViewModel.subFolder,
|
||||
itemList
|
||||
)
|
||||
}
|
||||
notifyItemChanged(position)//start showing anim!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.binding.trackName.text = "${if(item.title.length > 17){"${item.title.subSequence(0,16)}..."}else{item.title}}"
|
||||
holder.binding.artist.text = "${item.artists.get(0)}..."
|
||||
holder.binding.duration.text = "${item.durationSec/60} minutes, ${item.durationSec%60} sec"
|
||||
holder.binding.btnDownload.setOnClickListener{
|
||||
adapterScope.launch {
|
||||
// YTDownloadHelper.downloadFile(null,"YT_Downloads",item,format)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class YouTubeTrackDiffCallback: DiffUtil.ItemCallback<TrackDetails>(){
|
||||
|
@ -17,7 +17,12 @@
|
||||
|
||||
package com.shabinder.spotiflyer.ui.youtube
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -52,6 +57,8 @@ class YoutubeFragment : Fragment() {
|
||||
private lateinit var adapter : YoutubeTrackListAdapter
|
||||
private val sampleDomain1 = "youtube.com"
|
||||
private val sampleDomain2 = "youtu.be"
|
||||
private var intentFilter: IntentFilter? = null
|
||||
private var updateUIReceiver: BroadcastReceiver? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
@ -60,10 +67,11 @@ class YoutubeFragment : Fragment() {
|
||||
binding = DataBindingUtil.inflate(inflater,R.layout.youtube_fragment,container,false)
|
||||
youtubeViewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java)
|
||||
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
|
||||
adapter = YoutubeTrackListAdapter()
|
||||
adapter = YoutubeTrackListAdapter(youtubeViewModel)
|
||||
binding.trackList.adapter = adapter
|
||||
|
||||
initializeLiveDataObservers()
|
||||
initializeBroadcast()
|
||||
|
||||
val args = YoutubeFragmentArgs.fromBundle(requireArguments())
|
||||
val link = args.link
|
||||
@ -89,6 +97,10 @@ class YoutubeFragment : Fragment() {
|
||||
youtubeViewModel.getYTTrack(searchId,ytDownloader)
|
||||
}else{showToast("Your Youtube Link is not of a Video!!")}
|
||||
}
|
||||
|
||||
/*
|
||||
* Download All Tracks
|
||||
* */
|
||||
binding.btnDownloadAll.setOnClickListener {
|
||||
binding.btnDownloadAll.visibility = View.GONE
|
||||
binding.downloadingFab.visibility = View.VISIBLE
|
||||
@ -105,7 +117,7 @@ class YoutubeFragment : Fragment() {
|
||||
sharedViewModel.uiScope.launch(Dispatchers.Default){
|
||||
val urlList = arrayListOf<String>()
|
||||
youtubeViewModel.ytTrackList.value?.forEach { urlList.add("https://i.ytimg.com/vi/${it.albumArt.absolutePath.substringAfterLast("/")
|
||||
.substringBeforeLast(".")}/maxresdefault.jpg")}
|
||||
.substringBeforeLast(".")}/hqdefault.jpg")}
|
||||
//Appending Source
|
||||
urlList.add("youtube")
|
||||
loadAllImages(
|
||||
@ -122,7 +134,56 @@ class YoutubeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
initializeBroadcast()
|
||||
}
|
||||
|
||||
private fun initializeBroadcast() {
|
||||
intentFilter = IntentFilter()
|
||||
intentFilter?.addAction("track_download_completed")
|
||||
|
||||
updateUIReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
//UI update here
|
||||
if (intent != null){
|
||||
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
|
||||
trackDetails?.let {
|
||||
val position: Int = youtubeViewModel.ytTrackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
|
||||
Log.i("Track","Download Completed Intent :$position")
|
||||
if(position != -1) {
|
||||
val track = youtubeViewModel.ytTrackList.value?.get(position)
|
||||
track?.let{
|
||||
it.downloaded = DownloadStatus.Downloaded
|
||||
youtubeViewModel.ytTrackList.value?.set(position, it)
|
||||
adapter.notifyItemChanged(position)
|
||||
checkIfAllDownloaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
requireActivity().registerReceiver(updateUIReceiver, intentFilter)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
requireActivity().unregisterReceiver(updateUIReceiver)
|
||||
}
|
||||
|
||||
private fun checkIfAllDownloaded() {
|
||||
if(!youtubeViewModel.ytTrackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
|
||||
//All Tracks Downloaded
|
||||
binding.btnDownloadAll.visibility = View.GONE
|
||||
binding.downloadingFab.apply{
|
||||
setImageResource(R.drawable.ic_tick)
|
||||
visibility = View.VISIBLE
|
||||
clearAnimation()
|
||||
keepScreenOn = false
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun initializeLiveDataObservers() {
|
||||
/**
|
||||
* CoverUrl Binding Observer!
|
||||
|
@ -32,6 +32,7 @@ import com.shabinder.spotiflyer.models.Source
|
||||
import com.shabinder.spotiflyer.models.TrackDetails
|
||||
import com.shabinder.spotiflyer.utils.Provider
|
||||
import com.shabinder.spotiflyer.utils.Provider.defaultDir
|
||||
import com.shabinder.spotiflyer.utils.Provider.showToast
|
||||
import com.shabinder.spotiflyer.utils.finalOutputDir
|
||||
import com.shabinder.spotiflyer.utils.removeIllegalChars
|
||||
import kotlinx.coroutines.*
|
||||
@ -41,10 +42,11 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
|
||||
|
||||
/*
|
||||
* YT Album Art Schema
|
||||
* Normal Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
|
||||
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
|
||||
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
|
||||
* */
|
||||
|
||||
val ytTrackList = MutableLiveData<List<TrackDetails>>()
|
||||
val ytTrackList = MutableLiveData<MutableList<TrackDetails>>()
|
||||
val format = MutableLiveData<Format>()
|
||||
private val loading = "Loading"
|
||||
var title = MutableLiveData<String>().apply { value = "\"Loading!\"" }
|
||||
@ -56,81 +58,101 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
|
||||
|
||||
|
||||
fun getYTPlaylist(searchId:String, ytDownloader:YoutubeDownloader){
|
||||
uiScope.launch(Dispatchers.IO) {
|
||||
Log.i("YT Playlist",searchId)
|
||||
val playlist = ytDownloader.getPlaylist(searchId)
|
||||
val playlistDetails = playlist.details()
|
||||
val name = playlistDetails.title()
|
||||
subFolder = removeIllegalChars(name).toString()
|
||||
val videos = playlist.videos()
|
||||
coverUrl.postValue("https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/maxresdefault.jpg")
|
||||
title.postValue(
|
||||
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
|
||||
)
|
||||
ytTrackList.postValue(videos.map {
|
||||
TrackDetails(
|
||||
title = it.title(),
|
||||
artists = listOf(it.author().toString()),
|
||||
durationSec = it.lengthSeconds(),
|
||||
albumArt = File(
|
||||
Environment.getExternalStorageDirectory(),
|
||||
defaultDir +".Images/" + it.videoId() + ".jpeg"
|
||||
),
|
||||
source = Source.YouTube,
|
||||
downloaded = if(File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists())
|
||||
DownloadStatus.Downloaded
|
||||
else DownloadStatus.NotDownloaded
|
||||
try{
|
||||
uiScope.launch(Dispatchers.IO) {
|
||||
Log.i("YT Playlist",searchId)
|
||||
val playlist = ytDownloader.getPlaylist(searchId)
|
||||
val playlistDetails = playlist.details()
|
||||
val name = playlistDetails.title()
|
||||
subFolder = removeIllegalChars(name).toString()
|
||||
val videos = playlist.videos()
|
||||
coverUrl.postValue("https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg")
|
||||
title.postValue(
|
||||
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
|
||||
)
|
||||
})
|
||||
ytTrackList.postValue(videos.map {
|
||||
TrackDetails(
|
||||
title = it.title(),
|
||||
artists = listOf(it.author().toString()),
|
||||
durationSec = it.lengthSeconds(),
|
||||
albumArt = File(
|
||||
Environment.getExternalStorageDirectory(),
|
||||
defaultDir + ".Images/" + it.videoId() + ".jpeg"
|
||||
),
|
||||
source = Source.YouTube,
|
||||
downloaded = if (File(
|
||||
finalOutputDir(
|
||||
itemName = it.title(),
|
||||
type = folderType,
|
||||
subFolder = subFolder
|
||||
)
|
||||
).exists()
|
||||
)
|
||||
DownloadStatus.Downloaded
|
||||
else {
|
||||
DownloadStatus.NotDownloaded
|
||||
}
|
||||
)
|
||||
}.toMutableList())
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
databaseDAO.insert(DownloadRecord(
|
||||
type = "PlayList",
|
||||
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
|
||||
link = "https://www.youtube.com/playlist?list=$searchId",
|
||||
coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/maxresdefault.jpg",
|
||||
totalFiles = videos.size,
|
||||
directory = finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder),
|
||||
downloaded = File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists()
|
||||
))
|
||||
databaseDAO.insert(DownloadRecord(
|
||||
type = "PlayList",
|
||||
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
|
||||
link = "https://www.youtube.com/playlist?list=$searchId",
|
||||
coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg",
|
||||
totalFiles = videos.size,
|
||||
directory = finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder),
|
||||
downloaded = File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists()
|
||||
))
|
||||
}
|
||||
}
|
||||
}catch (e:com.github.kiulian.downloader.YoutubeException.BadPageException){
|
||||
showToast("An Error Occurred While Processing!")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun getYTTrack(searchId:String, ytDownloader:YoutubeDownloader) {
|
||||
uiScope.launch(Dispatchers.IO) {
|
||||
Log.i("YT Video",searchId)
|
||||
val video = ytDownloader.getVideo(searchId)
|
||||
coverUrl.postValue("https://i.ytimg.com/vi/$searchId/maxresdefault.jpg")
|
||||
val detail = video?.details()
|
||||
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: ""
|
||||
Log.i("YT View Model",detail.toString())
|
||||
ytTrackList.postValue(listOf(TrackDetails(
|
||||
title = name,
|
||||
artists = listOf(detail?.author().toString()),
|
||||
durationSec = detail?.lengthSeconds()?:0,
|
||||
albumArt = File(
|
||||
Environment.getExternalStorageDirectory(),
|
||||
Provider.defaultDir +".Images/" + searchId + ".jpeg"
|
||||
),
|
||||
source = Source.YouTube
|
||||
)))
|
||||
title.postValue(
|
||||
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
|
||||
)
|
||||
try{
|
||||
uiScope.launch(Dispatchers.IO) {
|
||||
Log.i("YT Video",searchId)
|
||||
val video = ytDownloader.getVideo(searchId)
|
||||
coverUrl.postValue("https://i.ytimg.com/vi/$searchId/hqdefault.jpg")
|
||||
val detail = video?.details()
|
||||
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: ""
|
||||
Log.i("YT View Model",detail.toString())
|
||||
ytTrackList.postValue(
|
||||
listOf(TrackDetails(
|
||||
title = name,
|
||||
artists = listOf(detail?.author().toString()),
|
||||
durationSec = detail?.lengthSeconds()?:0,
|
||||
albumArt = File(
|
||||
Environment.getExternalStorageDirectory(),
|
||||
Provider.defaultDir +".Images/" + searchId + ".jpeg"
|
||||
),
|
||||
source = Source.YouTube
|
||||
)).toMutableList()
|
||||
)
|
||||
title.postValue(
|
||||
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
|
||||
)
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
databaseDAO.insert(DownloadRecord(
|
||||
type = "Track",
|
||||
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
|
||||
link = "https://www.youtube.com/watch?v=$searchId",
|
||||
coverUrl = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg",
|
||||
totalFiles = 1,
|
||||
downloaded = false,
|
||||
directory = finalOutputDir(type = "YT_Downloads")
|
||||
))
|
||||
withContext(Dispatchers.IO){
|
||||
databaseDAO.insert(DownloadRecord(
|
||||
type = "Track",
|
||||
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
|
||||
link = "https://www.youtube.com/watch?v=$searchId",
|
||||
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
|
||||
totalFiles = 1,
|
||||
downloaded = false,
|
||||
directory = finalOutputDir(type = "YT_Downloads")
|
||||
))
|
||||
}
|
||||
}
|
||||
} catch (e:com.github.kiulian.downloader.YoutubeException){
|
||||
showToast("An Error Occurred While Processing!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package com.shabinder.spotiflyer.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import com.github.kiulian.downloader.YoutubeDownloader
|
||||
import com.shabinder.spotiflyer.App
|
||||
import com.shabinder.spotiflyer.MainActivity
|
||||
@ -117,4 +118,7 @@ object Provider {
|
||||
return retrofit.create(YoutubeMusicApi::class.java)
|
||||
}
|
||||
|
||||
fun showToast(string: String,long:Boolean=false){
|
||||
Toast.makeText(activity,string,if(long)Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ import android.view.animation.RotateAnimation
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.databinding.BindingAdapter
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
@ -77,8 +76,7 @@ fun rotateAnim(view: View){
|
||||
view.animation = rotate
|
||||
}
|
||||
|
||||
@BindingAdapter("imageUrl")
|
||||
fun bindImage(imgView: ImageView, imgUrl: String?,source: Source) {
|
||||
fun bindImage(imgView: ImageView, imgUrl: String?,source: Source?) {
|
||||
imgUrl?.let {
|
||||
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
|
||||
Glide
|
||||
@ -122,6 +120,10 @@ fun bindImage(imgView: ImageView, imgUrl: String?,source: Source) {
|
||||
defaultDir+".Images/" + imgUrl.substringBeforeLast('/',imgUrl).substringAfterLast('/',imgUrl) + ".jpeg"
|
||||
)
|
||||
}
|
||||
else -> File(
|
||||
Environment.getExternalStorageDirectory(),
|
||||
defaultDir+".Images/" + imgUrl.substringAfterLast('/',imgUrl) + ".jpeg"
|
||||
)
|
||||
}
|
||||
// the File to save , append increasing numeric counter to prevent files from getting overwritten.
|
||||
resource?.copyTo(file)
|
||||
|
Loading…
Reference in New Issue
Block a user