From 297cc537108f5d5f528c16b8c9a5ab3ffd54c824 Mon Sep 17 00:00:00 2001
From: deniscerri <64997243+deniscerri@users.noreply.github.com>
Date: Sun, 11 Jun 2023 23:37:47 +0200
Subject: [PATCH] added playlist support from piped and fixed some format bugs
---
README.MD | 4 +-
app/build.gradle | 1 -
.../ytdlnis/adapter/ActiveDownloadAdapter.kt | 36 +---
.../database/models/VideoPreferences.kt | 2 +-
.../database/repository/ResultRepository.kt | 15 +-
.../database/viewmodel/DownloadViewModel.kt | 22 ++-
.../database/viewmodel/ResultViewModel.kt | 9 +-
.../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 30 ++--
.../downloadcard/CutVideoBottomSheetDialog.kt | 2 +-
.../more/settings/FolderSettingsFragment.kt | 7 +-
.../com/deniscerri/ytdlnis/util/FileUtil.kt | 13 +-
.../com/deniscerri/ytdlnis/util/InfoUtil.kt | 167 ++++++------------
.../deniscerri/ytdlnis/work/DownloadWorker.kt | 6 +-
.../main/res/layout/active_download_card.xml | 31 +---
.../res/layout/fragment_download_command.xml | 1 +
app/src/main/res/values/strings.xml | 2 +-
app/src/main/res/values/styles.xml | 12 +-
.../main/res/xml/processing_preferences.xml | 4 +-
18 files changed, 127 insertions(+), 237 deletions(-)
diff --git a/README.MD b/README.MD
index ce7c8127..a9bbc3b9 100644
--- a/README.MD
+++ b/README.MD
@@ -1,5 +1,5 @@
-
+
YTDLnis
@@ -11,7 +11,7 @@
[](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://ytdlnis.en.uptodown.com/android/download))

[](https://github.com/deniscerri/ytdlnis/releases)
diff --git a/app/build.gradle b/app/build.gradle
index e4c0f495..e69f6d4a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -181,5 +181,4 @@ dependencies {
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
- implementation "com.anggrayudi:storage:1.5.4"
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt
index 0304fe65..4678e731 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt
@@ -24,6 +24,7 @@ import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import com.squareup.picasso.Picasso
+import java.lang.StringBuilder
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener
@@ -91,10 +92,9 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
DownloadViewModel.Type.command -> type.setIconResource(R.drawable.ic_terminal)
}
- val formatNote = card.findViewById(R.id.format_note)
- formatNote.text = item.format.format_note.uppercase()
+ val formatDetailsChip = card.findViewById(R.id.format_note)
+ val formatDetailsText = StringBuilder(item.format.format_note.uppercase()+"\t")
- val codec = card.findViewById(R.id.codec)
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
@@ -103,34 +103,14 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
} else {
item.format.acodec.uppercase()
}
- if (codecText == "" || codecText == "none"){
- codec.visibility = View.GONE
- }else{
- codec.visibility = View.VISIBLE
- codec.text = codecText
+ if (codecText != "" && codecText != "none"){
+ formatDetailsText.append("\t|\t$codecText")
}
- val fileSize = card.findViewById(R.id.file_size)
- fileSize.text = FileUtil.convertFileSize(item.format.filesize)
- if (fileSize.text == "?") fileSize.visibility = View.GONE
+ val fileSize = FileUtil.convertFileSize(item.format.filesize)
+ if (fileSize != "?") formatDetailsText.append("\t|\t$fileSize")
- if (fileSize.visibility == View.VISIBLE && codec.visibility == View.GONE){
- 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()
- }
+ formatDetailsChip.text = formatDetailsText
//OUTPUT
val output = card.findViewById(R.id.output)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt
index 3661993a..2dd351ae 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt
@@ -6,7 +6,7 @@ data class VideoPreferences (
var splitByChapters: Boolean = false,
var sponsorBlockFilters: ArrayList = arrayListOf(),
var writeSubs: Boolean = false,
- var subsLanguages: String = "en.*,.*-orig",
+ var subsLanguages: String = "all",
var audioFormatIDs : ArrayList = arrayListOf(),
var removeAudio: Boolean = false
)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
index 21ba7047..3c19d8b7 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
@@ -9,11 +9,12 @@ import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.ResultItem
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) {
private val tag: String = "ResultRepository"
val allResults : LiveData> = resultDao.getResults()
- var itemCount = MutableLiveData(-1)
+ var itemCount = MutableStateFlow(-1)
suspend fun insert(it: ResultItem){
resultDao.insert(it)
@@ -27,7 +28,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
deleteAll()
val infoUtil = InfoUtil(context)
val items = infoUtil.getTrending(context)
- itemCount.postValue(items.size)
+ itemCount.value = items.size
for (i in items){
resultDao.insert(i!!)
}
@@ -38,7 +39,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
try{
if (resetResults) deleteAll()
val res = infoUtil.search(inputQuery)
- itemCount.postValue(res.size)
+ itemCount.value = res.size
res.forEach {
resultDao.insert(it!!)
}
@@ -54,7 +55,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
val v = infoUtil.getVideo(query)
if (resetResults) {
deleteAll()
- itemCount.postValue(1)
+ itemCount.value = 1
}else{
v!!.playlistTitle = "ytdlnis-Search"
}
@@ -81,10 +82,10 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
if (tmpToken == nextPageToken) break
nextPageToken = tmpToken
} while (true)
- itemCount.postValue(items.size)
items.forEach {
resultDao.insert(it!!)
}
+ itemCount.value = items.size
return items
}
@@ -94,7 +95,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
val items = infoUtil.getFromYTDL(inputQuery)
if (resetResults) {
deleteAll()
- itemCount.postValue(items.size)
+ itemCount.value = items.size
}else{
items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
}
@@ -113,7 +114,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
}
suspend fun deleteAll(){
- itemCount.postValue(0)
+ itemCount.value = 0
resultDao.deleteAll()
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
index 98abfee6..a4360095 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
@@ -94,13 +94,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val videoFormat = resources.getStringArray(R.array.video_formats)
+ val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
var videoContainer = sharedPreferences.getString("video_format", "Default")
if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue)
defaultVideoFormats = mutableListOf()
- videoFormat.forEach {
+ videoFormat.forEachIndexed { index , it ->
val tmp = Format(
- it,
+ videoFormatValues[index],
videoContainer!!,
"",
"",
@@ -116,7 +117,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
var audioContainer = sharedPreferences.getString("audio_format", "mp3")
if (audioContainer == "Default") audioContainer = App.instance.getString(R.string.defaultValue)
bestAudioFormat = Format(
- resources.getString(R.string.best_quality),
+ "best",
audioContainer!!,
"",
"",
@@ -155,8 +156,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
val downloadPath = when(type){
- Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefautAudioPath())
- Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefautVideoPath())
+ Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())
+ Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())
else -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
}
@@ -263,11 +264,18 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
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 audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
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,
historyItem.url,
historyItem.title,
@@ -279,7 +287,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
container,
"",
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
)
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
index 0c965902..8edee5dd 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
@@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
+import androidx.compose.runtime.MutableState
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
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.SearchHistoryRepository
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.regex.Pattern
class ResultViewModel(application: Application) : AndroidViewModel(application) {
private val tag: String = "ResultViewModel"
- private val repository : ResultRepository
+ val repository : ResultRepository
private val searchHistoryRepository : SearchHistoryRepository
val items : LiveData>
val loadingItems = MutableLiveData()
- var itemCount : LiveData
private val sharedPreferences: SharedPreferences
init {
@@ -36,7 +37,6 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
items = repository.allResults
loadingItems.postValue(false)
- itemCount = repository.itemCount
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
}
@@ -65,7 +65,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
if (inputQueries.size == 1){
parseQuery(inputQueries[0], true)
}else {
- repository.itemCount.postValue(inputQueries.size)
+ repository.itemCount.value = inputQueries.size
loadingItems.postValue(true)
inputQueries.forEach {
parseQuery(it, false)
@@ -90,7 +90,6 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
}
"Playlist" -> {
res = repository.getPlaylist(inputQuery, resetResults)
-
}
"Default" -> {
res = repository.getDefault(inputQuery, resetResults)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
index 2a989faf..8ebf2480 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
@@ -148,13 +148,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
resultViewModel.items.observe(viewLifecycleOwner) {
homeAdapter!!.submitList(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){
downloadAllFabCoordinator!!.visibility = VISIBLE
}else{
downloadAllFabCoordinator!!.visibility = GONE
}
- }else if (resultViewModel.itemCount.value!! == 1){
+ }else if (resultViewModel.repository.itemCount.value == 1){
if (sharedPreferences!!.getBoolean("download_card", true)){
if(it.size == 1 && quickLaunchSheet && parentFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
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 -> {
lifecycleScope.launch {
- val downloadList = withContext(Dispatchers.IO){
- downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!)
+ if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects!!.size == 1) {
+ 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()
actionMode?.finish()
-
- if (sharedPreferences!!.getBoolean("download_card", true)) {
- val bottomSheet = DownloadMultipleBottomSheetDialog(selectedObjects!!, downloadList.toMutableList())
- bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
- } else {
- downloadViewModel.queueDownloads(downloadList)
- }
}
true
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt
index ff001f45..9c052d88 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt
@@ -198,8 +198,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
try{
val listType: Type = object : TypeToken>() {}.type
chapters = Gson().fromJson(data.first().toString(), listType)
+ data.removeFirst()
}catch (ignored: Exception) {}
- data.removeFirst()
}
if (data.isEmpty()) throw Exception("No Streaming URL found!")
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt
index 00d6267d..31358427 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt
@@ -11,18 +11,15 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceManager
-import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.util.FileUtil
-import com.deniscerri.ytdlnis.work.DownloadWorker
import com.deniscerri.ytdlnis.work.MoveCacheFilesWorker
import com.google.android.material.snackbar.Snackbar
import java.io.File
-import java.util.concurrent.TimeUnit
class FolderSettingsFragment : BaseSettingsFragment() {
@@ -55,10 +52,10 @@ class FolderSettingsFragment : BaseSettingsFragment() {
moveCache = findPreference("move_cache")
if (preferences.getString("music_path", "")!!.isEmpty()) {
- editor.putString("music_path", FileUtil.getDefautAudioPath())
+ editor.putString("music_path", FileUtil.getDefaultAudioPath())
}
if (preferences.getString("video_path", "")!!.isEmpty()) {
- editor.putString("video_path", FileUtil.getDefautVideoPath())
+ editor.putString("video_path", FileUtil.getDefaultVideoPath())
}
if (preferences.getString("command_path", "")!!.isEmpty()) {
editor.putString("command_path", FileUtil.getDefaultCommandPath())
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt
index 2f94c292..c2f4af2c 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt
@@ -8,8 +8,6 @@ import android.os.Environment
import android.provider.DocumentsContract
import android.util.Log
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.database.models.DownloadItem
import okhttp3.internal.closeQuietly
@@ -19,12 +17,14 @@ import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
import java.util.*
import kotlin.math.log10
import kotlin.math.pow
object FileUtil {
+
fun deleteFile(path: String){
val file = File(path)
if (file.exists()) {
@@ -134,7 +134,7 @@ object FileUtil {
if (Build.VERSION.SDK_INT >= 26 ){
Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}else{
- it.moveTo(context, destFile.parent!!, destFile.name, FileCallback.ConflictResolution.REPLACE)
+ it.renameTo(destFile)
}
fileList.add(destFile)
}
@@ -183,11 +183,11 @@ object FileUtil {
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"
}
- fun getDefautVideoPath() : String{
+ fun getDefaultVideoPath() : String{
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Video"
}
@@ -199,6 +199,7 @@ object FileUtil {
if (s <= 0) return "?"
val units = arrayOf("B", "kB", "MB", "GB", "TB")
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]}"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
index af4c7b50..69d0b81a 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
@@ -6,7 +6,6 @@ import android.os.Looper
import android.text.Html
import android.util.Log
import android.widget.Toast
-import androidx.core.text.HtmlCompat
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ChapterItem
@@ -16,9 +15,13 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
+import okhttp3.ResponseBody
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
+import retrofit2.Call
+import retrofit2.Retrofit
+import retrofit2.http.GET
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
@@ -32,32 +35,23 @@ import java.util.regex.Pattern
class InfoUtil(private val context: Context) {
private var items: ArrayList
private lateinit var sharedPreferences: SharedPreferences
+ private lateinit var retrofit : Retrofit
private var key: String? = null
- private var useInvidous = false
init {
try {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
key = sharedPreferences.getString("api_key", "")
countryCODE = sharedPreferences.getString("locale", "")!!
+ if (countryCODE.isEmpty()) countryCODE = "US"
} catch (e: Exception) {
e.printStackTrace()
}
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 {
- init()
items = ArrayList()
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
return if (searchEngine == "ytsearch"){
@@ -122,7 +116,7 @@ class InfoUtil(private val context: Context) {
@Throws(JSONException::class)
fun searchFromPiped(query: String): ArrayList {
- val data = genericRequest(pipedURL + "/search?q=" + query + "?filter=videos")
+ val data = genericRequest("$pipedURL/search?q=$query?filter=videos")
val dataArray = data.getJSONArray("items")
if (dataArray.length() == 0) return getFromYTDL(query)
for (i in 0 until dataArray.length()) {
@@ -141,14 +135,27 @@ class InfoUtil(private val context: Context) {
@Throws(JSONException::class)
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
try{
- init()
items = ArrayList()
if (key!!.isEmpty()) {
- return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
- "",
- getFromYTDL("https://www.youtube.com/playlist?list=$id")
- )
+ // -------------- PIPED API FUNCTION -------------------
+ var url = ""
+ 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"
//short data
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)
fun getVideo(id: String): ResultItem? {
try {
- init()
+
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(
res, id
)
@@ -282,71 +267,17 @@ class InfoUtil(private val context: Context) {
}
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 = 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? {
var video: ResultItem? = null
try {
val title = Html.fromHtml(obj.getString("title").toString()).toString()
- val author = Html.fromHtml(obj.getString("uploader").toString()).toString()
- if (author.isBlank()) throw Exception()
+ val author = try {
+ 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 url = "https://www.youtube.com/watch?v=$id"
val formats : ArrayList = ArrayList()
@@ -358,8 +289,6 @@ class InfoUtil(private val context: Context) {
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
try{
formatObj.acodec = format.getString("codec")
- val streamURL = format.getString("url")
- formatObj.format_id = streamURL.substringAfter("itag=").substringBefore("&")
formatObj.asr = format.getString("quality")
if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){
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)
try{
formatObj.vcodec = format.getString("codec")
- val streamURL = format.getString("url")
- formatObj.format_id = streamURL.substringAfter("itag=").substringBefore("&")
}catch (e: Exception) {
e.printStackTrace()
}
@@ -391,6 +318,13 @@ class InfoUtil(private val context: Context) {
}
}
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()
if (obj.has("chapters") && obj.getJSONArray("chapters").length() > 0){
@@ -506,7 +440,7 @@ class InfoUtil(private val context: Context) {
@Throws(JSONException::class)
fun getTrending(context: Context): ArrayList {
- init()
+
items = ArrayList()
return if (key!!.isEmpty()) {
getTrendingFromPiped()
@@ -580,7 +514,7 @@ class InfoUtil(private val context: Context) {
}
fun getFormats(url: String) : List {
- init()
+
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
val m = p.matcher(url)
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
@@ -653,7 +587,7 @@ class InfoUtil(private val context: Context) {
}
fun getFormatsMultiple(urls: List, progress: (progress: List) -> Unit){
- init()
+
val urlsFile = File(context.cacheDir, "urls.txt")
urlsFile.delete()
urlsFile.createNewFile()
@@ -729,7 +663,6 @@ class InfoUtil(private val context: Context) {
}
fun getFromYTDL(query: String): ArrayList {
- init()
items = ArrayList()
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
try {
@@ -774,7 +707,7 @@ class InfoUtil(private val context: Context) {
var duration = ""
runCatching {
if (jsonObject.has("duration")) {
- duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.getDefault())
+ duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
}
}
val url = jsonObject.getString("webpage_url")
@@ -801,11 +734,14 @@ class InfoUtil(private val context: Context) {
format.put("filesize", 0)
}
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)) {
formatProper.format_note = format.getString("format_note")
}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
@@ -825,7 +761,6 @@ class InfoUtil(private val context: Context) {
var urls = "";
if(jsonObject.has("urls")) urls = jsonObject.getString("urls");
- Log.e("aa", formats.toString())
items.add(ResultItem(0,
url,
title,
@@ -846,7 +781,6 @@ class InfoUtil(private val context: Context) {
}
e.printStackTrace()
}
- Log.e("aa", items.toString())
return items
}
@@ -880,7 +814,7 @@ class InfoUtil(private val context: Context) {
var duration = ""
runCatching {
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 {
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 var countryCODE: String = ""
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
index 3de4c937..72b925d7 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
@@ -226,7 +226,7 @@ class DownloadWorker(
}
if (downloadItem.videoPreferences.embedSubs) {
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)
@@ -265,7 +265,9 @@ class DownloadWorker(
request.addOption("--write-auto-subs")
request.addOption("--sub-format", "str/ass/best")
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 &&
diff --git a/app/src/main/res/layout/active_download_card.xml b/app/src/main/res/layout/active_download_card.xml
index 1fce7ca5..84477d8f 100644
--- a/app/src/main/res/layout/active_download_card.xml
+++ b/app/src/main/res/layout/active_download_card.xml
@@ -96,36 +96,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:shapeAppearance="@style/ShapeAppearanceOverlay.Chip.RoundedStart" />
-
-
-
-
-
+ app:shapeAppearance="@style/ShapeAppearanceOverlay.Chip.Rounded" />
diff --git a/app/src/main/res/layout/fragment_download_command.xml b/app/src/main/res/layout/fragment_download_command.xml
index afae490b..2694df1c 100644
--- a/app/src/main/res/layout/fragment_download_command.xml
+++ b/app/src/main/res/layout/fragment_download_command.xml
@@ -55,6 +55,7 @@
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4ff143df..358d698d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -277,6 +277,6 @@
Transfer cached files to the downloads folder
All items must either be audio or video to use this option
Piped Instance
- Write a PIPED API Server, the app can use for youtube queries and formats
+ Write a PIPED API Server, the app can use for YouTube queries and formats
Use Custom Audio Quality
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a75b93fb..364aa517 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -31,19 +31,11 @@
- 5dp
-
-
-
diff --git a/app/src/main/res/xml/processing_preferences.xml b/app/src/main/res/xml/processing_preferences.xml
index c66e7bfa..26fb92ee 100644
--- a/app/src/main/res/xml/processing_preferences.xml
+++ b/app/src/main/res/xml/processing_preferences.xml
@@ -39,10 +39,8 @@