added playlist support from piped and fixed some format bugs

This commit is contained in:
deniscerri 2023-06-11 23:37:47 +02:00
parent b7c2ae0100
commit 297cc53710
No known key found for this signature in database
GPG key ID: 95C43D517D830350
18 changed files with 127 additions and 237 deletions

View file

@ -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 @@
[![Github Download](https://custom-icon-badges.herokuapp.com/badge/Download-blue?style=for-the-badge&logo=download&logoColor=white)](https://github.com/deniscerri/ytdlnis/releases/latest) [![Github Download](https://custom-icon-badges.herokuapp.com/badge/Download-blue?style=for-the-badge&logo=download&logoColor=white)](https://github.com/deniscerri/ytdlnis/releases/latest)
[![IzzyOnDroid Repo](https://custom-icon-badges.herokuapp.com/badge/IzzyOnDroid%20Repo-red?style=for-the-badge&logo=download&logoColor=white)](https://android.izzysoft.de/repo/apk/com.deniscerri.ytdl) [![IzzyOnDroid Repo](https://custom-icon-badges.herokuapp.com/badge/IzzyOnDroid%20Repo-red?style=for-the-badge&logo=download&logoColor=white)](https://android.izzysoft.de/repo/apk/com.deniscerri.ytdl)
[![UpToDown](https://custom-icon-badges.herokuapp.com/badge/UpToDown-green?style=for-the-badge&logo=download&logoColor=white)]([https://android.izzysoft.de/repo/apk/com.deniscerri.ytdl](https://ytdlnis.en.uptodown.com/android/download))
![CI](https://github.com/deniscerri/ytdlnis/actions/workflows/android.yml/badge.svg?branch=main&event=pull) ![CI](https://github.com/deniscerri/ytdlnis/actions/workflows/android.yml/badge.svg?branch=main&event=pull)
[![preview release](https://img.shields.io/github/release/deniscerri/ytdlnis.svg?maxAge=3600&include_prereleases&label=preview)](https://github.com/deniscerri/ytdlnis/releases) [![preview release](https://img.shields.io/github/release/deniscerri/ytdlnis.svg?maxAge=3600&include_prereleases&label=preview)](https://github.com/deniscerri/ytdlnis/releases)

View file

@ -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"
} }

View file

@ -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)

View file

@ -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
) )

View file

@ -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()
} }

View file

@ -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
) )
} }

View file

@ -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)

View file

@ -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
} }

View file

@ -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!")

View file

@ -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())

View file

@ -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]}"
} }
} }

View file

@ -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&regionCode=$countryCODE&playlistId=$id&key=$key" val url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken=$nextPageToken&maxResults=50&regionCode=$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 = ""
} }

View file

@ -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 &&

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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