added playlist support from piped and fixed some format bugs
This commit is contained in:
parent
b7c2ae0100
commit
297cc53710
18 changed files with 127 additions and 237 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<img src="fastlane/metadata/android/en-US/images/icon.png" width="30%" /> <br>
|
<img src="fastlane/metadata/android/en-US/images/icon.png" width="25%" /> <br>
|
||||||
YTDLnis
|
YTDLnis
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
[](https://github.com/deniscerri/ytdlnis/releases/latest)
|
[](https://github.com/deniscerri/ytdlnis/releases/latest)
|
||||||
[](https://android.izzysoft.de/repo/apk/com.deniscerri.ytdl)
|
[](https://android.izzysoft.de/repo/apk/com.deniscerri.ytdl)
|
||||||
|
[]([https://android.izzysoft.de/repo/apk/com.deniscerri.ytdl](https://ytdlnis.en.uptodown.com/android/download))
|
||||||
|
|
||||||

|

|
||||||
[](https://github.com/deniscerri/ytdlnis/releases)
|
[](https://github.com/deniscerri/ytdlnis/releases)
|
||||||
|
|
|
||||||
|
|
@ -181,5 +181,4 @@ dependencies {
|
||||||
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
|
||||||
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
||||||
implementation "com.anggrayudi:storage:1.5.4"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.google.android.material.shape.CornerFamily
|
import com.google.android.material.shape.CornerFamily
|
||||||
import com.google.android.material.shape.ShapeAppearanceModel
|
import com.google.android.material.shape.ShapeAppearanceModel
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
|
import java.lang.StringBuilder
|
||||||
|
|
||||||
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
|
|
@ -91,10 +92,9 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
DownloadViewModel.Type.command -> type.setIconResource(R.drawable.ic_terminal)
|
DownloadViewModel.Type.command -> type.setIconResource(R.drawable.ic_terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
val formatNote = card.findViewById<Chip>(R.id.format_note)
|
val formatDetailsChip = card.findViewById<Chip>(R.id.format_note)
|
||||||
formatNote.text = item.format.format_note.uppercase()
|
val formatDetailsText = StringBuilder(item.format.format_note.uppercase()+"\t")
|
||||||
|
|
||||||
val codec = card.findViewById<Chip>(R.id.codec)
|
|
||||||
val codecText =
|
val codecText =
|
||||||
if (item.format.encoding != "") {
|
if (item.format.encoding != "") {
|
||||||
item.format.encoding.uppercase()
|
item.format.encoding.uppercase()
|
||||||
|
|
@ -103,34 +103,14 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
} else {
|
} else {
|
||||||
item.format.acodec.uppercase()
|
item.format.acodec.uppercase()
|
||||||
}
|
}
|
||||||
if (codecText == "" || codecText == "none"){
|
if (codecText != "" && codecText != "none"){
|
||||||
codec.visibility = View.GONE
|
formatDetailsText.append("\t|\t$codecText")
|
||||||
}else{
|
|
||||||
codec.visibility = View.VISIBLE
|
|
||||||
codec.text = codecText
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileSize = card.findViewById<Chip>(R.id.file_size)
|
val fileSize = FileUtil.convertFileSize(item.format.filesize)
|
||||||
fileSize.text = FileUtil.convertFileSize(item.format.filesize)
|
if (fileSize != "?") formatDetailsText.append("\t|\t$fileSize")
|
||||||
if (fileSize.text == "?") fileSize.visibility = View.GONE
|
|
||||||
|
|
||||||
if (fileSize.visibility == View.VISIBLE && codec.visibility == View.GONE){
|
formatDetailsChip.text = formatDetailsText
|
||||||
fileSize.shapeAppearanceModel = ShapeAppearanceModel.builder()
|
|
||||||
.setTopLeftCorner(CornerFamily.ROUNDED, 0F)
|
|
||||||
.setBottomLeftCorner(CornerFamily.ROUNDED, 0F)
|
|
||||||
.setTopRightCorner(CornerFamily.ROUNDED, 20F)
|
|
||||||
.setBottomRightCorner(CornerFamily.ROUNDED, 20F)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileSize.visibility == View.GONE && codec.visibility == View.GONE){
|
|
||||||
formatNote.shapeAppearanceModel = ShapeAppearanceModel.builder()
|
|
||||||
.setTopLeftCorner(CornerFamily.ROUNDED, 40F)
|
|
||||||
.setBottomLeftCorner(CornerFamily.ROUNDED, 40F)
|
|
||||||
.setTopRightCorner(CornerFamily.ROUNDED, 40F)
|
|
||||||
.setBottomRightCorner(CornerFamily.ROUNDED, 40F)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
//OUTPUT
|
//OUTPUT
|
||||||
val output = card.findViewById<TextView>(R.id.output)
|
val output = card.findViewById<TextView>(R.id.output)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ data class VideoPreferences (
|
||||||
var splitByChapters: Boolean = false,
|
var splitByChapters: Boolean = false,
|
||||||
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
||||||
var writeSubs: Boolean = false,
|
var writeSubs: Boolean = false,
|
||||||
var subsLanguages: String = "en.*,.*-orig",
|
var subsLanguages: String = "all",
|
||||||
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||||
var removeAudio: Boolean = false
|
var removeAudio: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@ import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) {
|
class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) {
|
||||||
private val tag: String = "ResultRepository"
|
private val tag: String = "ResultRepository"
|
||||||
val allResults : LiveData<List<ResultItem>> = resultDao.getResults()
|
val allResults : LiveData<List<ResultItem>> = resultDao.getResults()
|
||||||
var itemCount = MutableLiveData(-1)
|
var itemCount = MutableStateFlow(-1)
|
||||||
|
|
||||||
suspend fun insert(it: ResultItem){
|
suspend fun insert(it: ResultItem){
|
||||||
resultDao.insert(it)
|
resultDao.insert(it)
|
||||||
|
|
@ -27,7 +28,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
deleteAll()
|
deleteAll()
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val items = infoUtil.getTrending(context)
|
val items = infoUtil.getTrending(context)
|
||||||
itemCount.postValue(items.size)
|
itemCount.value = items.size
|
||||||
for (i in items){
|
for (i in items){
|
||||||
resultDao.insert(i!!)
|
resultDao.insert(i!!)
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +39,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
try{
|
try{
|
||||||
if (resetResults) deleteAll()
|
if (resetResults) deleteAll()
|
||||||
val res = infoUtil.search(inputQuery)
|
val res = infoUtil.search(inputQuery)
|
||||||
itemCount.postValue(res.size)
|
itemCount.value = res.size
|
||||||
res.forEach {
|
res.forEach {
|
||||||
resultDao.insert(it!!)
|
resultDao.insert(it!!)
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +55,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
val v = infoUtil.getVideo(query)
|
val v = infoUtil.getVideo(query)
|
||||||
if (resetResults) {
|
if (resetResults) {
|
||||||
deleteAll()
|
deleteAll()
|
||||||
itemCount.postValue(1)
|
itemCount.value = 1
|
||||||
}else{
|
}else{
|
||||||
v!!.playlistTitle = "ytdlnis-Search"
|
v!!.playlistTitle = "ytdlnis-Search"
|
||||||
}
|
}
|
||||||
|
|
@ -81,10 +82,10 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
if (tmpToken == nextPageToken) break
|
if (tmpToken == nextPageToken) break
|
||||||
nextPageToken = tmpToken
|
nextPageToken = tmpToken
|
||||||
} while (true)
|
} while (true)
|
||||||
itemCount.postValue(items.size)
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
resultDao.insert(it!!)
|
resultDao.insert(it!!)
|
||||||
}
|
}
|
||||||
|
itemCount.value = items.size
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +95,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
val items = infoUtil.getFromYTDL(inputQuery)
|
val items = infoUtil.getFromYTDL(inputQuery)
|
||||||
if (resetResults) {
|
if (resetResults) {
|
||||||
deleteAll()
|
deleteAll()
|
||||||
itemCount.postValue(items.size)
|
itemCount.value = items.size
|
||||||
}else{
|
}else{
|
||||||
items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
|
items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +114,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteAll(){
|
suspend fun deleteAll(){
|
||||||
itemCount.postValue(0)
|
itemCount.value = 0
|
||||||
resultDao.deleteAll()
|
resultDao.deleteAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,13 +94,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
|
|
||||||
val videoFormat = resources.getStringArray(R.array.video_formats)
|
val videoFormat = resources.getStringArray(R.array.video_formats)
|
||||||
|
val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
|
||||||
var videoContainer = sharedPreferences.getString("video_format", "Default")
|
var videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||||
if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue)
|
if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue)
|
||||||
|
|
||||||
defaultVideoFormats = mutableListOf()
|
defaultVideoFormats = mutableListOf()
|
||||||
videoFormat.forEach {
|
videoFormat.forEachIndexed { index , it ->
|
||||||
val tmp = Format(
|
val tmp = Format(
|
||||||
it,
|
videoFormatValues[index],
|
||||||
videoContainer!!,
|
videoContainer!!,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
|
@ -116,7 +117,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
var audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
var audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||||
if (audioContainer == "Default") audioContainer = App.instance.getString(R.string.defaultValue)
|
if (audioContainer == "Default") audioContainer = App.instance.getString(R.string.defaultValue)
|
||||||
bestAudioFormat = Format(
|
bestAudioFormat = Format(
|
||||||
resources.getString(R.string.best_quality),
|
"best",
|
||||||
audioContainer!!,
|
audioContainer!!,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
|
@ -155,8 +156,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadPath = when(type){
|
val downloadPath = when(type){
|
||||||
Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefautAudioPath())
|
Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())
|
||||||
Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefautVideoPath())
|
Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())
|
||||||
else -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
|
else -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,11 +264,18 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val defaultPath = when(historyItem.type) {
|
||||||
|
Type.audio -> FileUtil.getDefaultAudioPath()
|
||||||
|
Type.video -> FileUtil.getDefaultVideoPath()
|
||||||
|
Type.command -> FileUtil.getDefaultCommandPath()
|
||||||
|
}
|
||||||
|
|
||||||
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
|
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
|
||||||
|
|
||||||
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
|
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
|
||||||
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs)
|
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs)
|
||||||
|
val downloadPath = File(historyItem.downloadPath)
|
||||||
|
val path = if (downloadPath.exists()) downloadPath.parent else defaultPath
|
||||||
return DownloadItem(0,
|
return DownloadItem(0,
|
||||||
historyItem.url,
|
historyItem.url,
|
||||||
historyItem.title,
|
historyItem.title,
|
||||||
|
|
@ -279,7 +287,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
container,
|
container,
|
||||||
"",
|
"",
|
||||||
ArrayList(),
|
ArrayList(),
|
||||||
File(historyItem.downloadPath).parent!!, historyItem.website, "", "", audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0
|
path, historyItem.website, "", "", audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
|
@ -16,17 +17,17 @@ import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class ResultViewModel(application: Application) : AndroidViewModel(application) {
|
class ResultViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val tag: String = "ResultViewModel"
|
private val tag: String = "ResultViewModel"
|
||||||
private val repository : ResultRepository
|
val repository : ResultRepository
|
||||||
private val searchHistoryRepository : SearchHistoryRepository
|
private val searchHistoryRepository : SearchHistoryRepository
|
||||||
val items : LiveData<List<ResultItem>>
|
val items : LiveData<List<ResultItem>>
|
||||||
val loadingItems = MutableLiveData<Boolean>()
|
val loadingItems = MutableLiveData<Boolean>()
|
||||||
var itemCount : LiveData<Int>
|
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -36,7 +37,6 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
|
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
|
||||||
items = repository.allResults
|
items = repository.allResults
|
||||||
loadingItems.postValue(false)
|
loadingItems.postValue(false)
|
||||||
itemCount = repository.itemCount
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
if (inputQueries.size == 1){
|
if (inputQueries.size == 1){
|
||||||
parseQuery(inputQueries[0], true)
|
parseQuery(inputQueries[0], true)
|
||||||
}else {
|
}else {
|
||||||
repository.itemCount.postValue(inputQueries.size)
|
repository.itemCount.value = inputQueries.size
|
||||||
loadingItems.postValue(true)
|
loadingItems.postValue(true)
|
||||||
inputQueries.forEach {
|
inputQueries.forEach {
|
||||||
parseQuery(it, false)
|
parseQuery(it, false)
|
||||||
|
|
@ -90,7 +90,6 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
}
|
}
|
||||||
"Playlist" -> {
|
"Playlist" -> {
|
||||||
res = repository.getPlaylist(inputQuery, resetResults)
|
res = repository.getPlaylist(inputQuery, resetResults)
|
||||||
|
|
||||||
}
|
}
|
||||||
"Default" -> {
|
"Default" -> {
|
||||||
res = repository.getDefault(inputQuery, resetResults)
|
res = repository.getDefault(inputQuery, resetResults)
|
||||||
|
|
|
||||||
|
|
@ -148,13 +148,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
resultViewModel.items.observe(viewLifecycleOwner) {
|
resultViewModel.items.observe(viewLifecycleOwner) {
|
||||||
homeAdapter!!.submitList(it)
|
homeAdapter!!.submitList(it)
|
||||||
resultsList = it
|
resultsList = it
|
||||||
if(resultViewModel.itemCount.value!! > 1 || resultViewModel.itemCount.value!! == -1){
|
if(resultViewModel.repository.itemCount.value > 1 || resultViewModel.repository.itemCount.value == -1){
|
||||||
if (it.size > 1 && it[0].playlistTitle.isNotEmpty() && it[0].playlistTitle != getString(R.string.trendingPlaylist) && !loadingItems){
|
if (it.size > 1 && it[0].playlistTitle.isNotEmpty() && it[0].playlistTitle != getString(R.string.trendingPlaylist) && !loadingItems){
|
||||||
downloadAllFabCoordinator!!.visibility = VISIBLE
|
downloadAllFabCoordinator!!.visibility = VISIBLE
|
||||||
}else{
|
}else{
|
||||||
downloadAllFabCoordinator!!.visibility = GONE
|
downloadAllFabCoordinator!!.visibility = GONE
|
||||||
}
|
}
|
||||||
}else if (resultViewModel.itemCount.value!! == 1){
|
}else if (resultViewModel.repository.itemCount.value == 1){
|
||||||
if (sharedPreferences!!.getBoolean("download_card", true)){
|
if (sharedPreferences!!.getBoolean("download_card", true)){
|
||||||
if(it.size == 1 && quickLaunchSheet && parentFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
|
if(it.size == 1 && quickLaunchSheet && parentFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
|
||||||
showSingleDownloadSheet(it[0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
|
showSingleDownloadSheet(it[0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
|
||||||
|
|
@ -626,18 +626,26 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}
|
}
|
||||||
R.id.download -> {
|
R.id.download -> {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val downloadList = withContext(Dispatchers.IO){
|
if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects!!.size == 1) {
|
||||||
downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!)
|
showSingleDownloadSheet(selectedObjects!![0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
|
||||||
|
}else{
|
||||||
|
val downloadList = withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||||
|
if (selectedObjects!!.size == 1){
|
||||||
|
showSingleDownloadSheet(selectedObjects!![0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
|
||||||
|
}else{
|
||||||
|
val bottomSheet = DownloadMultipleBottomSheetDialog(selectedObjects!!, downloadList.toMutableList())
|
||||||
|
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadViewModel.queueDownloads(downloadList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clearCheckedItems()
|
clearCheckedItems()
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
|
|
||||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
|
||||||
val bottomSheet = DownloadMultipleBottomSheetDialog(selectedObjects!!, downloadList.toMutableList())
|
|
||||||
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
|
|
||||||
} else {
|
|
||||||
downloadViewModel.queueDownloads(downloadList)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,8 +198,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
try{
|
try{
|
||||||
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
|
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
|
||||||
chapters = Gson().fromJson(data.first().toString(), listType)
|
chapters = Gson().fromJson(data.first().toString(), listType)
|
||||||
|
data.removeFirst()
|
||||||
}catch (ignored: Exception) {}
|
}catch (ignored: Exception) {}
|
||||||
data.removeFirst()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.isEmpty()) throw Exception("No Streaming URL found!")
|
if (data.isEmpty()) throw Exception("No Streaming URL found!")
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,15 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
|
||||||
import com.deniscerri.ytdlnis.work.MoveCacheFilesWorker
|
import com.deniscerri.ytdlnis.work.MoveCacheFilesWorker
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
class FolderSettingsFragment : BaseSettingsFragment() {
|
class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
|
|
@ -55,10 +52,10 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
moveCache = findPreference("move_cache")
|
moveCache = findPreference("move_cache")
|
||||||
|
|
||||||
if (preferences.getString("music_path", "")!!.isEmpty()) {
|
if (preferences.getString("music_path", "")!!.isEmpty()) {
|
||||||
editor.putString("music_path", FileUtil.getDefautAudioPath())
|
editor.putString("music_path", FileUtil.getDefaultAudioPath())
|
||||||
}
|
}
|
||||||
if (preferences.getString("video_path", "")!!.isEmpty()) {
|
if (preferences.getString("video_path", "")!!.isEmpty()) {
|
||||||
editor.putString("video_path", FileUtil.getDefautVideoPath())
|
editor.putString("video_path", FileUtil.getDefaultVideoPath())
|
||||||
}
|
}
|
||||||
if (preferences.getString("command_path", "")!!.isEmpty()) {
|
if (preferences.getString("command_path", "")!!.isEmpty()) {
|
||||||
editor.putString("command_path", FileUtil.getDefaultCommandPath())
|
editor.putString("command_path", FileUtil.getDefaultCommandPath())
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import android.os.Environment
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import com.anggrayudi.storage.callback.FileCallback
|
|
||||||
import com.anggrayudi.storage.file.moveTo
|
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import okhttp3.internal.closeQuietly
|
import okhttp3.internal.closeQuietly
|
||||||
|
|
@ -19,12 +17,14 @@ import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
import java.text.DecimalFormatSymbols
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
object FileUtil {
|
object FileUtil {
|
||||||
|
|
||||||
fun deleteFile(path: String){
|
fun deleteFile(path: String){
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
|
|
@ -134,7 +134,7 @@ object FileUtil {
|
||||||
if (Build.VERSION.SDK_INT >= 26 ){
|
if (Build.VERSION.SDK_INT >= 26 ){
|
||||||
Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
}else{
|
}else{
|
||||||
it.moveTo(context, destFile.parent!!, destFile.name, FileCallback.ConflictResolution.REPLACE)
|
it.renameTo(destFile)
|
||||||
}
|
}
|
||||||
fileList.add(destFile)
|
fileList.add(destFile)
|
||||||
}
|
}
|
||||||
|
|
@ -183,11 +183,11 @@ object FileUtil {
|
||||||
return File(context.filesDir.absolutePath + """/logs/Terminal - ${titleRegex.replace(command.take(30), "")}##terminal.log""")
|
return File(context.filesDir.absolutePath + """/logs/Terminal - ${titleRegex.replace(command.take(30), "")}##terminal.log""")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDefautAudioPath() : String{
|
fun getDefaultAudioPath() : String{
|
||||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Audio"
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Audio"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDefautVideoPath() : String{
|
fun getDefaultVideoPath() : String{
|
||||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Video"
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Video"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,6 +199,7 @@ object FileUtil {
|
||||||
if (s <= 0) return "?"
|
if (s <= 0) return "?"
|
||||||
val units = arrayOf("B", "kB", "MB", "GB", "TB")
|
val units = arrayOf("B", "kB", "MB", "GB", "TB")
|
||||||
val digitGroups = (log10(s.toDouble()) / log10(1024.0)).toInt()
|
val digitGroups = (log10(s.toDouble()) / log10(1024.0)).toInt()
|
||||||
return DecimalFormat("#,##0.#").format(s / 1024.0.pow(digitGroups.toDouble())) + " " + units[digitGroups]
|
val symbols = DecimalFormatSymbols(Locale.US)
|
||||||
|
return "${DecimalFormat("#,##0.#", symbols).format(s / 1024.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ import android.os.Looper
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.text.HtmlCompat
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||||
|
|
@ -16,9 +15,13 @@ import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
|
import okhttp3.ResponseBody
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.http.GET
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
|
@ -32,32 +35,23 @@ import java.util.regex.Pattern
|
||||||
class InfoUtil(private val context: Context) {
|
class InfoUtil(private val context: Context) {
|
||||||
private var items: ArrayList<ResultItem?>
|
private var items: ArrayList<ResultItem?>
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
private lateinit var retrofit : Retrofit
|
||||||
private var key: String? = null
|
private var key: String? = null
|
||||||
private var useInvidous = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
try {
|
try {
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
key = sharedPreferences.getString("api_key", "")
|
key = sharedPreferences.getString("api_key", "")
|
||||||
countryCODE = sharedPreferences.getString("locale", "")!!
|
countryCODE = sharedPreferences.getString("locale", "")!!
|
||||||
|
if (countryCODE.isEmpty()) countryCODE = "US"
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun init(){
|
|
||||||
if (countryCODE.isEmpty()){
|
|
||||||
countryCODE = "US"
|
|
||||||
}
|
|
||||||
if (!useInvidous){
|
|
||||||
val res = genericRequest(invidousURL + "stats")
|
|
||||||
useInvidous = res.length() != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun search(query: String): ArrayList<ResultItem?> {
|
fun search(query: String): ArrayList<ResultItem?> {
|
||||||
init()
|
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||||
return if (searchEngine == "ytsearch"){
|
return if (searchEngine == "ytsearch"){
|
||||||
|
|
@ -122,7 +116,7 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
||||||
val data = genericRequest(pipedURL + "/search?q=" + query + "?filter=videos")
|
val data = genericRequest("$pipedURL/search?q=$query?filter=videos")
|
||||||
val dataArray = data.getJSONArray("items")
|
val dataArray = data.getJSONArray("items")
|
||||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||||
for (i in 0 until dataArray.length()) {
|
for (i in 0 until dataArray.length()) {
|
||||||
|
|
@ -141,14 +135,27 @@ class InfoUtil(private val context: Context) {
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
||||||
try{
|
try{
|
||||||
init()
|
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
if (key!!.isEmpty()) {
|
if (key!!.isEmpty()) {
|
||||||
return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
|
// -------------- PIPED API FUNCTION -------------------
|
||||||
"",
|
var url = ""
|
||||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
||||||
)
|
else """$pipedURL/nextpage/playlists/$id?nextpage=${nextPageToken.replace("&prettyPrint", "%26prettyPrint")}"""
|
||||||
|
|
||||||
|
val res = genericRequest(url)
|
||||||
|
if (!res.has("relatedStreams")) throw Exception()
|
||||||
|
|
||||||
|
val dataArray = res.getJSONArray("relatedStreams")
|
||||||
|
var nextpage = res.getString("nextpage")
|
||||||
|
for (i in 0 until dataArray.length()){
|
||||||
|
val obj = dataArray.getJSONObject(i)
|
||||||
|
items.add(createVideoFromPipedJSON(obj, obj.getString("url").removePrefix("/watch?v=")))
|
||||||
|
}
|
||||||
|
if (nextpage == "null") nextpage = ""
|
||||||
|
return PlaylistTuple(nextpage, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------- YOUTUBE API FUNCTION --------------------------------
|
||||||
val url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken=$nextPageToken&maxResults=50®ionCode=$countryCODE&playlistId=$id&key=$key"
|
val url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken=$nextPageToken&maxResults=50®ionCode=$countryCODE&playlistId=$id&key=$key"
|
||||||
//short data
|
//short data
|
||||||
val res = genericRequest(url)
|
val res = genericRequest(url)
|
||||||
|
|
@ -206,34 +213,12 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlaylistFromInvidous(id: String): PlaylistTuple {
|
|
||||||
val url = invidousURL + "playlists/" + id
|
|
||||||
val res = genericRequest(url)
|
|
||||||
if (res.length() == 0) return PlaylistTuple(
|
|
||||||
"",
|
|
||||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
val vids = res.getJSONArray("videos")
|
|
||||||
for (i in 0 until vids.length()) {
|
|
||||||
val element = vids.getJSONObject(i)
|
|
||||||
val v = createVideoFromInvidiousJSON(element)
|
|
||||||
if (v == null || v.thumb.isEmpty()) continue
|
|
||||||
v.playlistTitle = res.getString("title")
|
|
||||||
items.add(v)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return PlaylistTuple("", items)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getVideo(id: String): ResultItem? {
|
fun getVideo(id: String): ResultItem? {
|
||||||
try {
|
try {
|
||||||
init()
|
|
||||||
if (key!!.isEmpty()) {
|
if (key!!.isEmpty()) {
|
||||||
val res = genericRequest(pipedURL + "/streams/" + id)
|
val res = genericRequest("$pipedURL/streams/$id")
|
||||||
return if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideoFromPipedJSON(
|
return if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideoFromPipedJSON(
|
||||||
res, id
|
res, id
|
||||||
)
|
)
|
||||||
|
|
@ -282,71 +267,17 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
return video
|
return video
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVideoFromInvidiousJSON(obj: JSONObject): ResultItem? {
|
|
||||||
var video: ResultItem? = null
|
|
||||||
try {
|
|
||||||
val id = obj.getString("videoId")
|
|
||||||
val title = Html.fromHtml(obj.getString("title").toString()).toString()
|
|
||||||
val author = Html.fromHtml(obj.getString("author").toString()).toString()
|
|
||||||
|
|
||||||
if (author.isBlank()) throw Exception()
|
|
||||||
|
|
||||||
val duration = formatIntegerDuration(obj.getInt("lengthSeconds"), Locale.getDefault())
|
|
||||||
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
|
||||||
val url = "https://www.youtube.com/watch?v=$id"
|
|
||||||
val formats : ArrayList<Format> = ArrayList()
|
|
||||||
if (obj.has("adaptiveFormats")){
|
|
||||||
val formatsInJSON = obj.getJSONArray("adaptiveFormats")
|
|
||||||
for (f in 0 until formatsInJSON.length()){
|
|
||||||
val format = formatsInJSON.getJSONObject(f)
|
|
||||||
if(!format.has("container")) continue
|
|
||||||
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
|
||||||
try{
|
|
||||||
if (!formatObj.format_note.contains("audio", ignoreCase = true)){
|
|
||||||
val codecs = "\"([^\"]*)\"".toRegex().find(format.getString("type").split(";")[1])!!.value.split(",")
|
|
||||||
if (codecs.size > 1){
|
|
||||||
formatObj.vcodec = codecs[0].replace("\"", "")
|
|
||||||
formatObj.acodec = codecs[1].replace("\"", "")
|
|
||||||
}else if (codecs.size == 1){
|
|
||||||
formatObj.vcodec = codecs[0].replace("\"", "")
|
|
||||||
formatObj.acodec = "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
formats.add(formatObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video = ResultItem(0,
|
|
||||||
url,
|
|
||||||
title,
|
|
||||||
author,
|
|
||||||
duration,
|
|
||||||
thumb,
|
|
||||||
"youtube",
|
|
||||||
"",
|
|
||||||
formats,
|
|
||||||
"",
|
|
||||||
ArrayList()
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.toString())
|
|
||||||
}
|
|
||||||
return video
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createVideoFromPipedJSON(obj: JSONObject, id: String): ResultItem? {
|
private fun createVideoFromPipedJSON(obj: JSONObject, id: String): ResultItem? {
|
||||||
var video: ResultItem? = null
|
var video: ResultItem? = null
|
||||||
try {
|
try {
|
||||||
val title = Html.fromHtml(obj.getString("title").toString()).toString()
|
val title = Html.fromHtml(obj.getString("title").toString()).toString()
|
||||||
val author = Html.fromHtml(obj.getString("uploader").toString()).toString()
|
val author = try {
|
||||||
if (author.isBlank()) throw Exception()
|
Html.fromHtml(obj.getString("uploader").toString()).toString()
|
||||||
|
}catch (e: Exception){
|
||||||
|
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
|
||||||
|
}
|
||||||
|
|
||||||
val duration = formatIntegerDuration(obj.getInt("duration"), Locale.getDefault())
|
val duration = formatIntegerDuration(obj.getInt("duration"), Locale.US)
|
||||||
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
||||||
val url = "https://www.youtube.com/watch?v=$id"
|
val url = "https://www.youtube.com/watch?v=$id"
|
||||||
val formats : ArrayList<Format> = ArrayList()
|
val formats : ArrayList<Format> = ArrayList()
|
||||||
|
|
@ -358,8 +289,6 @@ class InfoUtil(private val context: Context) {
|
||||||
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
try{
|
try{
|
||||||
formatObj.acodec = format.getString("codec")
|
formatObj.acodec = format.getString("codec")
|
||||||
val streamURL = format.getString("url")
|
|
||||||
formatObj.format_id = streamURL.substringAfter("itag=").substringBefore("&")
|
|
||||||
formatObj.asr = format.getString("quality")
|
formatObj.asr = format.getString("quality")
|
||||||
if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){
|
if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){
|
||||||
formatObj.format_note = format.getString("audioTrackName") + " Audio, " + formatObj.format_note
|
formatObj.format_note = format.getString("audioTrackName") + " Audio, " + formatObj.format_note
|
||||||
|
|
@ -382,8 +311,6 @@ class InfoUtil(private val context: Context) {
|
||||||
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
try{
|
try{
|
||||||
formatObj.vcodec = format.getString("codec")
|
formatObj.vcodec = format.getString("codec")
|
||||||
val streamURL = format.getString("url")
|
|
||||||
formatObj.format_id = streamURL.substringAfter("itag=").substringBefore("&")
|
|
||||||
}catch (e: Exception) {
|
}catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
@ -391,6 +318,13 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formats.sortBy { it.filesize }
|
formats.sortBy { it.filesize }
|
||||||
|
formats.groupBy { it.format_id }.forEach {
|
||||||
|
if (it.value.count() > 1) {
|
||||||
|
it.value.filter { f-> !f.format_note.contains("original", true) }.forEachIndexed { index, format -> format.format_id = format.format_id.split("-")[0] + "-${index}" }
|
||||||
|
val engDefault = it.value.find { f -> f.format_note.contains("original", true) }
|
||||||
|
engDefault?.format_id = (engDefault?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val chapters = ArrayList<ChapterItem>()
|
val chapters = ArrayList<ChapterItem>()
|
||||||
if (obj.has("chapters") && obj.getJSONArray("chapters").length() > 0){
|
if (obj.has("chapters") && obj.getJSONArray("chapters").length() > 0){
|
||||||
|
|
@ -506,7 +440,7 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getTrending(context: Context): ArrayList<ResultItem?> {
|
fun getTrending(context: Context): ArrayList<ResultItem?> {
|
||||||
init()
|
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
return if (key!!.isEmpty()) {
|
return if (key!!.isEmpty()) {
|
||||||
getTrendingFromPiped()
|
getTrendingFromPiped()
|
||||||
|
|
@ -580,7 +514,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFormats(url: String) : List<Format> {
|
fun getFormats(url: String) : List<Format> {
|
||||||
init()
|
|
||||||
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
||||||
val m = p.matcher(url)
|
val m = p.matcher(url)
|
||||||
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
||||||
|
|
@ -653,7 +587,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
|
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
|
||||||
init()
|
|
||||||
val urlsFile = File(context.cacheDir, "urls.txt")
|
val urlsFile = File(context.cacheDir, "urls.txt")
|
||||||
urlsFile.delete()
|
urlsFile.delete()
|
||||||
urlsFile.createNewFile()
|
urlsFile.createNewFile()
|
||||||
|
|
@ -729,7 +663,6 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
||||||
init()
|
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||||
try {
|
try {
|
||||||
|
|
@ -774,7 +707,7 @@ class InfoUtil(private val context: Context) {
|
||||||
var duration = ""
|
var duration = ""
|
||||||
runCatching {
|
runCatching {
|
||||||
if (jsonObject.has("duration")) {
|
if (jsonObject.has("duration")) {
|
||||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.getDefault())
|
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val url = jsonObject.getString("webpage_url")
|
val url = jsonObject.getString("webpage_url")
|
||||||
|
|
@ -801,11 +734,14 @@ class InfoUtil(private val context: Context) {
|
||||||
format.put("filesize", 0)
|
format.put("filesize", 0)
|
||||||
}
|
}
|
||||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
if (format.has("format_note") && formatProper.format_note != null){
|
if (formatProper.format_note == null) continue
|
||||||
|
if (format.has("format_note")){
|
||||||
if (!formatProper!!.format_note.contains("audio only", true)) {
|
if (!formatProper!!.format_note.contains("audio only", true)) {
|
||||||
formatProper.format_note = format.getString("format_note")
|
formatProper.format_note = format.getString("format_note")
|
||||||
}else{
|
}else{
|
||||||
formatProper.format_note = "${format.getString("format_note")} audio"
|
if (!formatProper.format_note.endsWith("audio", true)){
|
||||||
|
formatProper.format_note = "${format.getString("format_note")} audio"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (formatProper.format_note == "storyboard") continue
|
if (formatProper.format_note == "storyboard") continue
|
||||||
|
|
@ -825,7 +761,6 @@ class InfoUtil(private val context: Context) {
|
||||||
var urls = "";
|
var urls = "";
|
||||||
if(jsonObject.has("urls")) urls = jsonObject.getString("urls");
|
if(jsonObject.has("urls")) urls = jsonObject.getString("urls");
|
||||||
|
|
||||||
Log.e("aa", formats.toString())
|
|
||||||
items.add(ResultItem(0,
|
items.add(ResultItem(0,
|
||||||
url,
|
url,
|
||||||
title,
|
title,
|
||||||
|
|
@ -846,7 +781,6 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
Log.e("aa", items.toString())
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -880,7 +814,7 @@ class InfoUtil(private val context: Context) {
|
||||||
var duration = ""
|
var duration = ""
|
||||||
runCatching {
|
runCatching {
|
||||||
if (jsonObject.has("duration")) {
|
if (jsonObject.has("duration")) {
|
||||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.getDefault())
|
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1037,7 +971,6 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "API MANAGER"
|
private const val TAG = "API MANAGER"
|
||||||
private const val invidousURL = "https://invidious.baczek.me/api/v1/"
|
|
||||||
private const val defaultPipedURL = "https://pipedapi.kavin.rocks/"
|
private const val defaultPipedURL = "https://pipedapi.kavin.rocks/"
|
||||||
private var countryCODE: String = ""
|
private var countryCODE: String = ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
if (downloadItem.videoPreferences.embedSubs) {
|
if (downloadItem.videoPreferences.embedSubs) {
|
||||||
request.addOption("--embed-subs")
|
request.addOption("--embed-subs")
|
||||||
request.addOption("--sub-langs", "en.*,.*-orig")
|
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
||||||
}
|
}
|
||||||
val defaultFormats = context.resources.getStringArray(R.array.video_formats)
|
val defaultFormats = context.resources.getStringArray(R.array.video_formats)
|
||||||
|
|
||||||
|
|
@ -265,7 +265,9 @@ class DownloadWorker(
|
||||||
request.addOption("--write-auto-subs")
|
request.addOption("--write-auto-subs")
|
||||||
request.addOption("--sub-format", "str/ass/best")
|
request.addOption("--sub-format", "str/ass/best")
|
||||||
request.addOption("--convert-subtitles", "srt")
|
request.addOption("--convert-subtitles", "srt")
|
||||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
if (!downloadItem.videoPreferences.embedSubs) {
|
||||||
|
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.removeAudio &&
|
if (downloadItem.videoPreferences.removeAudio &&
|
||||||
|
|
|
||||||
|
|
@ -96,36 +96,7 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Chip.RoundedStart" />
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Chip.Rounded" />
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/codec"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:clickable="false"
|
|
||||||
android:textSize="11sp"
|
|
||||||
app:chipBackgroundColor="?attr/colorSurface"
|
|
||||||
app:chipMinTouchTargetSize="0dp"
|
|
||||||
app:chipStrokeWidth="0.5dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Chip.Middle" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/file_size"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:clickable="false"
|
|
||||||
android:textSize="11sp"
|
|
||||||
app:chipBackgroundColor="?attr/colorSurface"
|
|
||||||
app:chipMinTouchTargetSize="0dp"
|
|
||||||
app:chipStrokeWidth="0.5dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Chip.RoundedEnd" />
|
|
||||||
|
|
||||||
</com.google.android.material.chip.ChipGroup>
|
</com.google.android.material.chip.ChipGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
|
android:maxLines="2"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,6 @@
|
||||||
<string name="move_temporary_files_summary">Transfer cached files to the downloads folder</string>
|
<string name="move_temporary_files_summary">Transfer cached files to the downloads folder</string>
|
||||||
<string name="format_filtering_hint_2">All items must either be audio or video to use this option</string>
|
<string name="format_filtering_hint_2">All items must either be audio or video to use this option</string>
|
||||||
<string name="piped_instance">Piped Instance</string>
|
<string name="piped_instance">Piped Instance</string>
|
||||||
<string name="piped_instance_summary">Write a PIPED API Server, the app can use for youtube queries and formats</string>
|
<string name="piped_instance_summary">Write a PIPED API Server, the app can use for YouTube queries and formats</string>
|
||||||
<string name="custom_audio_quality">Use Custom Audio Quality</string>
|
<string name="custom_audio_quality">Use Custom Audio Quality</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -31,19 +31,11 @@
|
||||||
<item name="cornerSize">5dp</item>
|
<item name="cornerSize">5dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ShapeAppearanceOverlay.Chip.RoundedStart" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
<style name="ShapeAppearanceOverlay.Chip.Rounded" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||||
<item name="cornerFamily">rounded</item>
|
<item name="cornerFamily">rounded</item>
|
||||||
<item name="cornerSizeTopLeft">12dp</item>
|
<item name="cornerSizeTopLeft">12dp</item>
|
||||||
<item name="cornerSizeTopRight">0dp</item>
|
|
||||||
<item name="cornerSizeBottomLeft">12dp</item>
|
|
||||||
<item name="cornerSizeBottomRight">0dp</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ShapeAppearanceOverlay.Chip.RoundedEnd" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
|
||||||
<item name="cornerFamily">rounded</item>
|
|
||||||
<item name="cornerSizeTopLeft">0dp</item>
|
|
||||||
<item name="cornerSizeTopRight">12dp</item>
|
<item name="cornerSizeTopRight">12dp</item>
|
||||||
<item name="cornerSizeBottomLeft">0dp</item>
|
<item name="cornerSizeBottomLeft">12dp</item>
|
||||||
<item name="cornerSizeBottomRight">12dp</item>
|
<item name="cornerSizeBottomRight">12dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,8 @@
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:icon="@drawable/baseline_subject_24"
|
android:icon="@drawable/baseline_subject_24"
|
||||||
app:key="subs_lang"
|
app:key="subs_lang"
|
||||||
|
|
||||||
android:dependency="write_subtitles"
|
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:defaultValue="en.*,.*-orig"
|
app:defaultValue="all"
|
||||||
app:title="@string/subtitle_languages" />
|
app:title="@string/subtitle_languages" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue