new features

added a header on cancelled, errored, saved, scheduled tabs to show the count of items and their own separate menus to copy the urls and delete them all
fix app not starting a scheduled download when there were no paused items, strange ik
add socket timeout in download settings
fixed app not putting extra commands in the very end of the command list
home screen channel tab filter bug fixes
Add ability to Set default command template
This commit is contained in:
zaednasr 2024-10-19 20:40:22 +02:00
parent 02a5c0c253
commit 80672a2556
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
52 changed files with 371 additions and 142 deletions

View file

@ -273,6 +273,9 @@ interface DownloadDao {
@Query("UPDATE downloads SET downloadStartTime=0, status='Queued' where id in (:list)")
suspend fun resetScheduleTimeForItems(list: List<Long>)
@Query("UPDATE downloads SET downloadStartTime=0, status='Queued' WHERE status = 'Scheduled'")
suspend fun resetScheduleTimeForAllScheduledItems()
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
suspend fun reQueueDownloadItems(list: List<Long>)

View file

@ -754,6 +754,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
repository.startDownloadWorker(emptyList(), application)
}
suspend fun resetScheduleItemForAllScheduledItemsAndStartDownload() = CoroutineScope(Dispatchers.IO).launch {
dbManager.downloadDao.resetScheduleTimeForAllScheduledItems()
repository.startDownloadWorker(emptyList(), application)
}
suspend fun putAtTopOfQueue(ids: List<Long>) = CoroutineScope(Dispatchers.IO).launch{
val downloads = dao.getQueuedDownloadsListIDs()
val lastID = ids.maxOf { it }

View file

@ -28,7 +28,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
val sortOrder = MutableLiveData(SORTING.DESC)
val sortType = MutableLiveData(HistorySortType.DATE)
val websiteFilter = MutableLiveData("")
val statusFilter = MutableLiveData(HistoryStatus.UNSET)
val statusFilter = MutableLiveData(HistoryStatus.ALL)
private val queryFilter = MutableLiveData("")
private val typeFilter = MutableLiveData("")

View file

@ -913,7 +913,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
downloadViewModel.deleteDownload(id)
if (processingItemsCount > 0){
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO) {
processingItemsCount++
@ -976,7 +976,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
if (processingItemsCount > 0) {
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
processingItemsCount++
downloadViewModel.insert(deletedItem)

View file

@ -6,17 +6,23 @@ import android.content.DialogInterface
import android.content.SharedPreferences
import android.graphics.Canvas
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -36,6 +42,7 @@ import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
@ -60,6 +67,10 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
private var actionMode : ActionMode? = null
private var totalSize = 0
private lateinit var listHeader : ConstraintLayout
private lateinit var count : TextView
private lateinit var headerMenuBtn : TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -72,7 +83,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
return fragmentView
}
@SuppressLint("RestrictedApi")
@SuppressLint("RestrictedApi", "SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -100,8 +111,39 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
}
}
listHeader = view.findViewById(R.id.list_header)
count = view.findViewById(R.id.count)
headerMenuBtn = view.findViewById(R.id.dropdown_menu)
headerMenuBtn.setOnClickListener {
val popup = PopupMenu(activity, it)
popup.menuInflater.inflate(R.menu.cancelled_header_menu, popup.menu)
popup.setOnMenuItemClickListener { m ->
when(m.itemId){
R.id.delete_all -> {
UiUtil.showGenericDeleteAllDialog(requireContext()) {
downloadViewModel.deleteCancelled()
}
}
R.id.copy_urls -> {
lifecycleScope.launch {
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Cancelled))
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
}
}
}
true
}
popup.show()
}
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Cancelled)).observe(viewLifecycleOwner){
totalSize = it
listHeader.isVisible = it > 0
count.text = "$it ${getString(R.string.items)}"
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}
@ -331,7 +373,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
downloadViewModel.getItemByID(itemID)
}
downloadViewModel.deleteDownload(deletedItem.id)
Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -208,54 +208,15 @@ class DownloadQueueMainFragment : Fragment(){
try{
when(m.itemId){
R.id.clear_all -> {
showDeleteDialog {
UiUtil.showGenericDeleteAllDialog(requireContext()) {
downloadViewModel.deleteAll()
}
}
R.id.clear_queue -> {
showDeleteDialog {
UiUtil.showGenericDeleteAllDialog(requireContext()) {
downloadViewModel.cancelAllDownloads()
}
}
R.id.clear_cancelled -> {
showDeleteDialog {
downloadViewModel.deleteCancelled()
}
}
R.id.clear_scheduled -> {
showDeleteDialog {
downloadViewModel.deleteScheduled()
}
}
R.id.clear_errored -> {
showDeleteDialog {
downloadViewModel.deleteErrored()
}
}
R.id.clear_saved -> {
showDeleteDialog {
downloadViewModel.deleteSaved()
}
}
R.id.copy_urls -> {
lifecycleScope.launch {
val tabStatus = mapOf(
0 to listOf(DownloadRepository.Status.Active),
1 to listOf(DownloadRepository.Status.Queued),
2 to listOf(DownloadRepository.Status.Scheduled),
3 to listOf(DownloadRepository.Status.Cancelled),
4 to listOf(DownloadRepository.Status.Error),
5 to listOf(DownloadRepository.Status.Saved),
)
tabStatus[tabLayout.selectedTabPosition]?.apply {
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByStatus(this@apply)
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
}
}
}
}
}catch (e: Exception){
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
@ -265,16 +226,6 @@ class DownloadQueueMainFragment : Fragment(){
}
}
private fun showDeleteDialog (deleteClicked: (deleteClicked: Boolean) -> Unit){
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
deleteClicked(true)
}
deleteDialog.show()
}
fun scrollToActive(){
tabLayout.getTabAt(0)!!.select()
viewPager2.setCurrentItem(0, true)

View file

@ -14,10 +14,14 @@ import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.PopupMenu
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -60,6 +64,11 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
private lateinit var adapter : GenericDownloadAdapter
private var actionMode : ActionMode? = null
private var totalSize: Int = 0
private lateinit var listHeader : ConstraintLayout
private lateinit var count : TextView
private lateinit var headerMenuBtn : TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -72,7 +81,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
return fragmentView
}
@SuppressLint("RestrictedApi")
@SuppressLint("RestrictedApi", "SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -100,8 +109,39 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
}
}
listHeader = view.findViewById(R.id.list_header)
count = view.findViewById(R.id.count)
headerMenuBtn = view.findViewById(R.id.dropdown_menu)
headerMenuBtn.setOnClickListener {
val popup = PopupMenu(activity, it)
popup.menuInflater.inflate(R.menu.errored_header_menu, popup.menu)
popup.setOnMenuItemClickListener { m ->
when(m.itemId){
R.id.delete_all -> {
UiUtil.showGenericDeleteAllDialog(requireContext()) {
downloadViewModel.deleteErrored()
}
}
R.id.copy_urls -> {
lifecycleScope.launch {
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Error))
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
}
}
}
true
}
popup.show()
}
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Error)).observe(viewLifecycleOwner){
totalSize = it
listHeader.isVisible = it > 0
count.text = "$it ${getString(R.string.items)}"
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}
@ -334,7 +374,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
downloadViewModel.getItemByID(itemID)
}
downloadViewModel.deleteDownload(deletedItem.id)
Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -684,7 +684,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
if (!deleteFile){
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
historyViewModel.insert(deletedItem)
}.show()

View file

@ -154,7 +154,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
downloadViewModel.updateDownload(item)
}
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title.ifEmpty { item.url }, Snackbar.LENGTH_LONG)
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title.ifEmpty { item.url }, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO) {
downloadViewModel.deleteDownload(item.id)

View file

@ -12,11 +12,15 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -59,6 +63,11 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
private lateinit var noResults: RelativeLayout
private var actionMode : ActionMode? = null
private var totalSize : Int = 0
private lateinit var listHeader : ConstraintLayout
private lateinit var count : TextView
private lateinit var headerMenuBtn : TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -71,7 +80,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
return fragmentView
}
@SuppressLint("RestrictedApi")
@SuppressLint("RestrictedApi", "SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -100,8 +109,40 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
}
}
listHeader = view.findViewById(R.id.list_header)
count = view.findViewById(R.id.count)
headerMenuBtn = view.findViewById(R.id.dropdown_menu)
headerMenuBtn.setOnClickListener {
val popup = PopupMenu(activity, it)
popup.menuInflater.inflate(R.menu.saved_header_menu, popup.menu)
popup.setOnMenuItemClickListener { m ->
when(m.itemId){
R.id.delete_all -> {
UiUtil.showGenericDeleteAllDialog(requireContext()) {
downloadViewModel.deleteSaved()
}
}
R.id.copy_urls -> {
lifecycleScope.launch {
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Saved))
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
}
}
}
true
}
popup.show()
}
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Saved)).observe(viewLifecycleOwner){
totalSize = it
listHeader.isVisible = it > 0
count.text = "$it ${getString(R.string.items)}"
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}
@ -321,7 +362,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
downloadViewModel.getItemByID(itemID)
}
downloadViewModel.deleteDownload(deletedItem.id)
Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -12,11 +12,15 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -61,6 +65,10 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
private var actionMode : ActionMode? = null
private var totalSize = 0
private lateinit var listHeader : ConstraintLayout
private lateinit var count : TextView
private lateinit var headerMenuBtn : TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -73,7 +81,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
return fragmentView
}
@SuppressLint("RestrictedApi")
@SuppressLint("RestrictedApi", "SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -101,9 +109,47 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
}
}
listHeader = view.findViewById(R.id.list_header)
count = view.findViewById(R.id.count)
headerMenuBtn = view.findViewById(R.id.dropdown_menu)
headerMenuBtn.setOnClickListener {
val popup = PopupMenu(activity, it)
popup.menuInflater.inflate(R.menu.scheduled_header_menu, popup.menu)
popup.setOnMenuItemClickListener { m ->
when(m.itemId){
R.id.download_now -> {
lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.resetScheduleItemForAllScheduledItemsAndStartDownload()
}
}
}
R.id.delete_all -> {
UiUtil.showGenericDeleteAllDialog(requireContext()) {
downloadViewModel.deleteScheduled()
}
}
R.id.copy_urls -> {
lifecycleScope.launch {
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Scheduled))
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
}
}
}
true
}
popup.show()
}
lifecycleScope.launch {
downloadViewModel.scheduledDownloadsCount.collectLatest {
totalSize = it
listHeader.isVisible = it > 0
count.text = "$it ${getString(R.string.items)}"
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}
@ -336,7 +382,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
downloadViewModel.getItemByID(itemID)
}
downloadViewModel.deleteDownload(deletedItem.id)
Snackbar.make(scheduledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
Snackbar.make(scheduledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -240,7 +240,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
logViewModel.getItemById(items[position].id)
}
logViewModel.delete(deletedItem)
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO){
logViewModel.insert(deletedItem)

View file

@ -1637,6 +1637,16 @@ object UiUtil {
deleteDialog.show()
}
fun showGenericDeleteAllDialog(context: Context, accepted: () -> Unit){
val deleteDialog = MaterialAlertDialogBuilder(context)
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
accepted()
}
deleteDialog.show()
}
fun showRemoveHistoryItemDialog(item: HistoryItem, context: Activity, delete: (item: HistoryItem, deleteFile: Boolean) -> Unit){
val deleteFile = booleanArrayOf(false)
val deleteDialog = MaterialAlertDialogBuilder(context)

View file

@ -81,7 +81,8 @@ class YTDLPUtil(private val context: Context) {
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--compat-options", "manifest-filesize-approx")
request.addOption("--socket-timeout", "5")
val socketTimeout = sharedPreferences.getString("socket_timeout", "5")!!.ifEmpty { "5" }
request.addOption("--socket-timeout", socketTimeout)
if (sharedPreferences.getBoolean("force_ipv4", false)){
request.addOption("-4")
@ -240,7 +241,8 @@ class YTDLPUtil(private val context: Context) {
request.addOption("-a", urlsFile.absolutePath)
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
val socketTimeout = sharedPreferences.getString("socket_timeout", "5")!!.ifEmpty { "5" }
request.addOption("--socket-timeout", socketTimeout)
if (sharedPreferences.getBoolean("force_ipv4", false)){
request.addOption("-4")
@ -435,7 +437,8 @@ class YTDLPUtil(private val context: Context) {
request.addOption("--print", "%(.{urls,chapters})s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
val socketTimeout = sharedPreferences.getString("socket_timeout", "5")!!.ifEmpty { "5" }
request.addOption("--socket-timeout", socketTimeout)
if (sharedPreferences.getBoolean("force_ipv4", false)){
request.addOption("-4")
@ -524,6 +527,7 @@ class YTDLPUtil(private val context: Context) {
@SuppressLint("RestrictedApi")
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest {
val useItemURL = sharedPreferences.getBoolean("use_itemurl_instead_playlisturl", false)
var isPlaylistItem = false
val request = if (downloadItem.url.endsWith(".txt")) {
YoutubeDLRequest(listOf()).apply {
@ -532,6 +536,7 @@ class YTDLPUtil(private val context: Context) {
}else if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank() || useItemURL){
YoutubeDLRequest(downloadItem.url)
}else{
isPlaylistItem = true
YoutubeDLRequest(downloadItem.playlistURL!!).apply {
if(downloadItem.playlistIndex == null){
val matchPortion = downloadItem.url.split("/").last().split("=").last().split("&").first()
@ -588,6 +593,11 @@ class YTDLPUtil(private val context: Context) {
request.addOption("--no-resize-buffer")
}
val socketTimeout = sharedPreferences.getString("socket_timeout", "")!!
if (socketTimeout.isNotBlank()) {
request.addOption("--socket-timeout", socketTimeout)
}
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
@ -801,9 +811,10 @@ class YTDLPUtil(private val context: Context) {
metadataCommands.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
metadataCommands.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\\d+)")
metadataCommands.addOption("--parse-metadata", "%(album_artist,first_artist|)s:%(album_artist)s")
metadataCommands.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)")
if (isPlaylistItem) {
metadataCommands.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)")
}
}
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
@ -1081,21 +1092,21 @@ class YTDLPUtil(private val context: Context) {
else -> {}
}
if (metadataCommands.isNotEmpty()){
request.addCommands(metadataCommands)
}
if (downloadItem.extraCommands.isNotBlank() && downloadItem.type != DownloadViewModel.Type.command){
val cache = File(FileUtil.getCachePath(context))
cache.mkdirs()
val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}${UUID.randomUUID()}.txt")
conf.createNewFile()
conf.writeText(downloadItem.extraCommands)
request.addOption(
"--config-locations",
conf.absolutePath
)
val tmp = mutableListOf<String>()
tmp.addOption("--config-locations", conf.absolutePath)
request.addCommands(tmp)
}
if (metadataCommands.isNotEmpty()){
request.addCommands(metadataCommands)
}
return request
}
@ -1103,8 +1114,8 @@ class YTDLPUtil(private val context: Context) {
val playerClient = sharedPreferences.getString("youtube_player_client", "default,mediaconnect")!!
.ifEmpty { "default,mediaconnect" }
val extractorArgs = mutableListOf<String>()
extractorArgs.add("player_client:${playerClient}")
val lang = sharedPreferences.getString("app_language", "en")
extractorArgs.add("player_client=${playerClient}")
val lang = Locale.getDefault().language
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs.add("lang=$lang")
}

View file

@ -74,7 +74,7 @@ class DownloadWorker(
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val time = System.currentTimeMillis() + 6000
val queuedItems = dao.getQueuedScheduledDownloadsUntil(time)
val priorityItemIDs = inputData.getLongArray("priority_item_ids")!!.toMutableList()
val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList()
val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true)
// this is needed for observe sources call, so it wont create result items

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View file

@ -1,10 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/list_header"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/dropdown_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
app:drawableStartCompat="@drawable/baseline_more_horiz_24"
app:drawableTint="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/count"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView android:id="@+id/download_recyclerview"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
@ -16,4 +52,4 @@
<include layout="@layout/no_results"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>

View file

@ -10,6 +10,7 @@
<LinearLayout
android:id="@+id/format_filter_linear"
android:layout_width="match_parent"
android:layout_marginBottom="20dp"
android:layout_height="wrap_content"
android:orientation="vertical">
@ -66,7 +67,6 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="20dp"
android:text="@string/websites"
android:textSize="14sp"
android:textStyle="bold" />

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_all"
android:title="@string/clear_cancelled"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -7,42 +7,9 @@
android:icon="@drawable/ic_delete_all"
android:title="@string/remove_downloading"
app:showAsAction="never" />
<item
android:id="@+id/clear_cancelled"
android:icon="@drawable/ic_delete_all"
android:title="@string/clear_cancelled"
app:showAsAction="never" />
<item
android:id="@+id/clear_scheduled"
android:icon="@drawable/ic_delete_all"
android:title="@string/clear_scheduled"
app:showAsAction="never" />
<item
android:id="@+id/clear_errored"
android:icon="@drawable/ic_delete_all"
android:title="@string/clear_errored"
app:showAsAction="never" />
<item
android:id="@+id/clear_saved"
android:icon="@drawable/ic_delete_all"
android:title="@string/clear_saved"
app:showAsAction="never" />
<item
android:id="@+id/clear_all"
android:icon="@drawable/ic_delete_all"
android:title="@string/remove_all"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:icon="@drawable/ic_delete_all"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_all"
android:title="@string/clear_errored"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_all"
android:title="@string/clear_saved"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/download_now"
android:title="@string/download_now"
app:showAsAction="never" />
<item
android:id="@+id/delete_all"
android:title="@string/clear_scheduled"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">استخدام AlarmManager بدلًا من WorkManager</string>
<string name="use_alarm_manager_summary">يتعين تمكين هذا الخيار عندما تكون أداة الـ WorkManager مقيدًة من قبل مُصنِّع الجهاز أو إذا كانت المهام المجدولة ليست دقيقة جدًا</string>
<string name="use_item_url_not_playlist">استخدام رابط العنصر بدلًا من رابط قائمة التشغيل</string>
<string name="use_item_url_not_playlist_summary">مفيد لقوائم تشغيل LibreTube غير المتصلة بالإنترنت والتي لا يتعرف عليها yt-dlp، لن يتم تضمين البيانات الوصفية لقائمة التشغيل مع تمكين هذا الخيار</string>
<string name="use_item_url_not_playlist_summary">لن يتم تضمين البيانات الوصفية لقائمة التشغيل مع تمكين هذا الخيار</string>
<string name="recode_video">إعادة ترميز الفيديو</string>
<string name="recode_video_summary">يعيد ترميز ملف الفيديو إلى تنسيق الفيديو المحدد</string>
<string name="daily">يوميًا</string>

View file

@ -418,6 +418,6 @@
<string name="recode_video">Прекодиране на видео</string>
<string name="recode_video_summary">Перекодира видео файлът в указания видеоформат</string>
<string name="format_importance_note">Подреждане важността на елемента на формат. Това подреждане ще се използва, само когато приложението извлече форматите от картата за изтегляне и автоматично избере формата!</string>
<string name="use_item_url_not_playlist_summary">Полезно за офлайн плейлисти Libretube, които не се разпознават от yt-dlp. Метаданните на плейлиста няма да бъдат вградени, ако функция та е включена</string>
<string name="use_item_url_not_playlist_summary">Метаданните на плейлиста няма да бъдат вградени, ако функция та е включена</string>
<string name="data_fetching_extractor_youtube">Екстрактор за извличане на данни (YouTube)</string>
</resources>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">Použít AlarmManager místo WorkManageru pro plánování</string>
<string name="use_alarm_manager_summary">Funkci povolte, pokud je WorkManager omezen výrobcem zařízení nebo pokud naplánované úlohy nejsou příliš přesné</string>
<string name="use_item_url_not_playlist">Použít URL položky místo URL playlistu</string>
<string name="use_item_url_not_playlist_summary">Užitečné pro offline playlisty Libretube, které nejsou rozpoznány yt-dlp. Metadata playlistu nebudou vložena, pokud je tato funkce povolena</string>
<string name="use_item_url_not_playlist_summary">Metadata playlistu nebudou vložena, pokud je tato funkce povolena</string>
<string name="recode_video">Překódovat video</string>
<string name="recode_video_summary">Překóduje videosoubor do zadaného formátu videa</string>
<string name="daily">Denně</string>

View file

@ -418,5 +418,5 @@
<string name="recode_video_summary">Nimmt die Videodatei im bestimmten Format auf</string>
<string name="use_alarm_manager">AlarmManager anstatt WorkManager für die Zeitplanung nutzen</string>
<string name="use_item_url_not_playlist">Die Element URL anstatt die Playlist URL nutzen</string>
<string name="use_item_url_not_playlist_summary">Nützlich für Libretube Offline-Wiedergabelisten, die von yt-dlp nicht erkannt werden. Wiedergabelisten-Metadaten werden nicht eingebettet, wenn diese Option aktiviert ist</string>
<string name="use_item_url_not_playlist_summary">Wiedergabelisten-Metadaten werden nicht eingebettet, wenn diese Option aktiviert ist</string>
</resources>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">Utilizar AlarmManager en lugar de WorkManager para la programación</string>
<string name="use_alarm_manager_summary">Habilite esta opción si WorkManager está restringido por el proveedor de su dispositivo o si los trabajos programados no son demasiado precisos</string>
<string name="use_item_url_not_playlist">Utilizar la URL del artículo en lugar de la URL de la lista de reproducción</string>
<string name="use_item_url_not_playlist_summary">Útil para listas de reproducción sin conexión de Libretube que no son reconocidas por yt-dlp. Los metadatos de la lista de reproducción no se incrustarán con esta opción activada</string>
<string name="use_item_url_not_playlist_summary">Los metadatos de la lista de reproducción no se incrustarán con esta opción activada</string>
<string name="recode_video">Recodificar video</string>
<string name="recode_video_summary">Recodifica el archivo de vídeo al formato de vídeo especificado</string>
<string name="daily">Diariamente</string>

View file

@ -419,6 +419,6 @@
<string name="format_importance_note">Ajuste l\'ordre de priorité des formats. Cette ordre ne sera utilisée que lorsque l\'application récupère les formats dans la carte de téléchargement et sélectionne automatiquement le format!</string>
<string name="enable_alarm_permission">Vous devez activer la permission SCHEDULE_EXACT_ALARM dans les paramètres de l\'application.</string>
<string name="process_downloads_background">"Les téléchargements sont toujours en cours de chargement. Voulez-vous continuer à les charger en arrière-plan et commencer à télécharger? "</string>
<string name="use_item_url_not_playlist_summary">Utile pour les playlists hors-ligne de Libretube qui ne sont pas reconnues par yt-dlp. Les métadonnées de la liste de lecture ne seront pas intégrées quand ceci est activé</string>
<string name="use_item_url_not_playlist_summary">Les métadonnées de la liste de lecture ne seront pas intégrées quand ceci est activé</string>
<string name="use_item_url_not_playlist">Utiliser l\'URL de l\'élément au lieu de celui de la liste de lecture</string>
</resources>

View file

@ -408,7 +408,7 @@
\nफाइल में सहेजे गए डेटा को मिटाने और केवल उसका उपयोग करने के लिए \'रीसेट करें\' दबाएं।</string>
<string name="use_alarm_manager">शेड्यूलिंग के लिए WorkManager के बजाय AlarmManager का उपयोग करें</string>
<string name="use_alarm_manager_summary">यदि WorkManager आपके डिवाइस विक्रेता द्वारा प्रतिबंधित है या शेड्यूल किए गए कार्य बहुत सटीक नहीं हैं तो इसे सक्षम करें</string>
<string name="use_item_url_not_playlist_summary">Libretube ऑफ़लाइन प्लेलिस्ट के लिए उपयोगी है जो yt-dlp द्वारा पहचाने नहीं जाते हैं। इस सक्षम के साथ प्लेलिस्ट मेटाडेटा एम्बेड नहीं किया जाएगा</string>
<string name="use_item_url_not_playlist_summary">नहीं जाते हैं। इस सक्षम के साथ प्लेलिस्ट मेटाडेटा एम्बेड नहीं किया जाएगा</string>
<string name="use_item_url_not_playlist">प्लेलिस्ट URL के बजाय आइटम URL का उपयोग करें</string>
<string name="recode_video">वीडियो पुनःकोड करें</string>
<string name="recode_video_summary">वीडियो फ़ाइल को निर्दिष्ट वीडियो प्रारूप में पुनःकोडित करता है</string>

View file

@ -419,5 +419,5 @@
<string name="data_fetching_extractor_youtube">Adatszerző Extractor (YouTube)</string>
<string name="restore_info">Nyomja meg a \'Restore\' gombot hogy összeolvassza a lementett adatokat a jelenlegiekkel.
\nNyomja meg a \'Reset\' gombot ha csak a lementett adatokat kívánja megtartani.</string>
<string name="use_item_url_not_playlist_summary">Hasznos tud lenni olyan Libretube offline playlistekhez, amelyeket a yt-dlp nem ismer fel. Ennek bekapcsolásával a playlistek metadatája nem fog beágyazódni</string>
<string name="use_item_url_not_playlist_summary">Ennek bekapcsolásával a playlistek metadatája nem fog beágyazódni</string>
</resources>

View file

@ -413,7 +413,7 @@
<string name="use_alarm_manager_summary">Aktifkan ini jika WorkManager dibatasi oleh vendor perangkatmu atau penjadwalan tugas tidak terlalu tepat waktu</string>
<string name="daily">Tiap hari</string>
<string name="use_item_url_not_playlist">Gunakan URL Butir, bukannya URL Daftar Putar</string>
<string name="use_item_url_not_playlist_summary">Berguna untuk daftar putar luring (offline) LibreTube yang tidak dikenali oleh yt-dlp. Metadata daftar putar tidak akan ditanamkan kalau preferensi ini diaktifkan</string>
<string name="use_item_url_not_playlist_summary">Metadata daftar putar tidak akan ditanamkan kalau preferensi ini diaktifkan</string>
<string name="recode_video_summary">Mengkodekan ulang file video ke format video yang ditentukan</string>
<string name="recode_video">Rekodekan Video</string>
</resources>

View file

@ -415,7 +415,7 @@
<string name="location">Posizione</string>
<string name="enable_alarm_permission">È necessario abilitare l\'autorizzazione SCHEDULE_EXACT_ALARM nelle impostazioni dell\'app.</string>
<string name="use_item_url_not_playlist">Usa l\'URL dell\'elemento invece dell\'URL della playlist</string>
<string name="use_item_url_not_playlist_summary">Utile per le playlist offline di Libretube che non sono riconosciute da yt-dlp. I metadati della playlist non saranno incorporati se questa opzione è abilitata</string>
<string name="use_item_url_not_playlist_summary">I metadati della playlist non saranno incorporati se questa opzione è abilitata</string>
<string name="recode_video">Ricodifica Video</string>
<string name="recode_video_summary">Ricodifica il file video nel formato video specificato</string>
<string name="process_downloads_background">"I download sono ancora in fase di caricamento. Continuare a elaborarli in background e iniziare il download? "</string>

View file

@ -357,7 +357,7 @@
<string name="save_auto_subs">שמור כתוביות אוטומטיות</string>
<string name="display_over_apps_summary">‬הפוך את כרטיס ההורדה תמיד להופיע למעלה. הפעל זאת אם האפליקציה הפועלת כעת נסגרת כאשר כרטיס ההורדה מופיע</string>
<string name="no_audio">וידאו ללא שמע</string>
<string name="use_item_url_not_playlist_summary">שימושי עבור רשימות השמעה לא מקוונות של Libretube שאינן מזוהות על ידי yt-dlp. מטא נתונים של רשימת השמעה לא יוטמעו כאשר זה מופעל</string>
<string name="use_item_url_not_playlist_summary"> מטא נתונים של רשימת השמעה לא יוטמעו כאשר זה מופעל</string>
<string name="no_download_fragments">אל תוריד כפרגמנטים</string>
<string name="no_download_fragments_summary">אל תשתמש בקבצי part. ותכתוב ישירות לקובץ הפלט</string>
<string name="swipe_gestures_download_card">החלק תנועות בכרטיס ההורדה</string>

View file

@ -404,7 +404,7 @@
<string name="use_alarm_manager">時間管理に WorkManager でなく AlarmManager を使う</string>
<string name="use_alarm_manager_summary">WorkManager が提供者によりに制限されていたり、時間管理されたジョブの精度が良くないなら、これを有効にします</string>
<string name="queue">キュー</string>
<string name="use_item_url_not_playlist_summary">Libretube のオフライン再生リストは yt-dlp が認識できないため有用です。有効にすると、再生リストのメタデータは埋め込まれません</string>
<string name="use_item_url_not_playlist_summary">有効にすると、再生リストのメタデータは埋め込まれません</string>
<string name="use_item_url_not_playlist">再生リストのURLの代わりに個々の項目のURLを使用</string>
<string name="restore_info">復元は、保存済みデータと現在のデータを統合します。
\n初期化は、消去しファイル中の保存済みデータのみを使用します。</string>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">AlarmManager in plaats van WorkManager gebruiken voor planning</string>
<string name="use_alarm_manager_summary">Schakel dit in als WorkManager beperkt is door de leverancier van uw apparaat of als geplande taken niet al te nauwkeurig zijn</string>
<string name="use_item_url_not_playlist">Item-URL gebruiken in plaats van afspeel­lijst-URL</string>
<string name="use_item_url_not_playlist_summary">Handig voor offline afspeel­lijsten van LibreTube die niet worden herkend door yt-dlp. Meta­gegevens van de afspeel­lijst worden niet ingesloten als dit is ingeschakeld</string>
<string name="use_item_url_not_playlist_summary">Meta­gegevens van de afspeel­lijst worden niet ingesloten als dit is ingeschakeld</string>
<string name="recode_video">Video hercoderen</string>
<string name="recode_video_summary">Hercodeert het video­bestand naar het opgegeven video­formaat</string>
<string name="cleanup_leftover_downloads">Overgebleven downloads opschonen (geannuleerd, foutief)</string>

View file

@ -416,7 +416,7 @@
<string name="use_alarm_manager">ਸਮਾਂ-ਤਹਿ ਕਰਨ ਲਈ WorkManager ਦੀ ਬਜਾਏ AlarmManager ਦੀ ਵਰਤੋਂ ਕਰੋ</string>
<string name="use_alarm_manager_summary">ਇਸ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ ਜੇਕਰ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਵਿਕਰੇਤਾ ਦੁਆਰਾ WorkManager ਨੂੰ ਪ੍ਰਤਿਬੰਧਿਤ ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ ਸ਼ਡਿਊਲ ਕੀਤੇ ਜੌਬ ਬਹੁਤ ਸਟੀਕ ਨਹੀਂ ਹਨ</string>
<string name="use_item_url_not_playlist">ਪਲੇਲਿਸਟ URL ਦੀ ਬਜਾਏ ਆਈਟਮ URL ਦੀ ਵਰਤੋਂ ਕਰੋ</string>
<string name="use_item_url_not_playlist_summary">Libretube ਆਫਲਾਈਨ ਪਲੇਲਿਸਟਾਂ ਲਈ ਉਪਯੋਗੀ ਜੋ yt-dlp ਦੁਆਰਾ ਮਾਨਤਾ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹਨ। ਇਸਨੂੰ ਸਮਰੱਥ ਕਰਨ ਨਾਲ ਪਲੇਲਿਸਟ ਮੈਟਾਡੇਟਾ ਏਮਬੇਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ</string>
<string name="use_item_url_not_playlist_summary">ਦੁਆਰਾ ਮਾਨਤਾ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹਨ। ਇਸਨੂੰ ਸਮਰੱਥ ਕਰਨ ਨਾਲ ਪਲੇਲਿਸਟ ਮੈਟਾਡੇਟਾ ਏਮਬੇਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ</string>
<string name="recode_video">ਵੀਡੀਓ ਰੀਕੋਡ ਕਰੋ</string>
<string name="recode_video_summary">ਵੀਡੀਓ ਫਾਈਲ ਨੂੰ ਨਿਸ਼ਚਿਤ ਵੀਡੀਓ ਫਾਰਮੈਟ ਵਿੱਚ ਰੀਕੋਡ ਕਰਦਾ ਹੈ</string>
<string name="data_fetching_extractor_youtube">ਡਾਟਾ ਪ੍ਰਾਪਤੀ ਐਕਸਟਰੈਕਟਰ (ਯੂਟਿਊਬ)</string>

View file

@ -416,7 +416,7 @@
<string name="monthly">Co miesiąc</string>
<string name="continue_anyway">Tak czy inaczej, kontynuuj</string>
<string name="use_item_url_not_playlist">Użyj adresu URL elementu zamiast adresu URL listy odtwarzania</string>
<string name="use_item_url_not_playlist_summary">Przydatne dla list odtwarzania offline Libretube, które nie są rozpoznawane przez yt-dlp. Metadane listy odtwarzania nie zostaną osadzone po włączeniu tej opcji</string>
<string name="use_item_url_not_playlist_summary">Metadane listy odtwarzania nie zostaną osadzone po włączeniu tej opcji</string>
<string name="recode_video_summary">Przekodowuje plik wideo do określonego formatu wideo</string>
<string name="recode_video">Przekoduj Video</string>
</resources>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">Usar \'AlarmManager\' em vez de \'WorkManager\' para agendamento</string>
<string name="use_alarm_manager_summary">Habilite isto se o \"WorkManager\" for restrito pelo fornecedor do seu dispositivo ou se as tarefas agendadas não forem muito precisas</string>
<string name="use_item_url_not_playlist">Usar URL do item em vez do URL da playlist</string>
<string name="use_item_url_not_playlist_summary">Útil para playlists offline do LibreTube que não são reconhecidas pelo yt-dlp. Os metadados da playlist não serão adicionados com esta opção ativada</string>
<string name="use_item_url_not_playlist_summary">Os metadados da playlist não serão adicionados com esta opção ativada</string>
<string name="recode_video_summary">Recodifica o arquivo de vídeo para o formato especificado</string>
<string name="recode_video">Recodificar vídeo</string>
<string name="daily">Diariamente</string>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">Используйте AlarmManager вместо WorkManager для планирования</string>
<string name="use_alarm_manager_summary">Включите это, если WorkManager ограничен поставщиком вашего устройства или запланированные задания не слишком точны</string>
<string name="use_item_url_not_playlist">Используйте URL-адрес элемента вместо URL-адреса плейлиста</string>
<string name="use_item_url_not_playlist_summary">Полезно для автономных плейлистов Libretube, которые не распознаются yt-dlp. Метаданные плейлиста не будут встроены, если эта функция включена</string>
<string name="use_item_url_not_playlist_summary">Метаданные плейлиста не будут встроены, если эта функция включена</string>
<string name="recode_video">Перекодировать видео</string>
<string name="recode_video_summary">Перекодирует видеофайл в указанный видеоформат</string>
<string name="daily">Ежедневно</string>

View file

@ -408,7 +408,7 @@
<string name="queue">Pásmo</string>
<string name="use_alarm_manager">Použiť AlarmManager namiesto WorkManageru na plánovanie</string>
<string name="use_alarm_manager_summary">Zapnite túto funkciu, ak je WorkManager obmedzený výrobcom zariadenia alebo ak naplánované úlohy nie sú príliš presné</string>
<string name="use_item_url_not_playlist_summary">Užitočné pre offline zoznamy skladieb Libretube, ktoré yt-dlp nerozpoznáva. Metadáta playlistu nebudú vložené, ak je táto funkcia zapnutá</string>
<string name="use_item_url_not_playlist_summary">Metadáta playlistu nebudú vložené, ak je táto funkcia zapnutá</string>
<string name="use_item_url_not_playlist">Použiť URL položky namiesto URL playlistu</string>
<string name="recode_video">Prekódovať video</string>
<string name="recode_video_summary">Prekóduje videosúbor do určeného formátu videa</string>

View file

@ -407,7 +407,7 @@
<string name="use_alarm_manager">Përdor AlarmManager në vend të WorkManager për Skedulim</string>
<string name="use_alarm_manager_summary">Aktivizo këtë ku WorkManager është i limituar në paisje ose punët e skeduluara nuk ekzekutohen në kohë</string>
<string name="use_item_url_not_playlist">Përdor URL e videos në vend të URL së playlist</string>
<string name="use_item_url_not_playlist_summary">E dobishme për playlistet Libretube qe nuk njihen nga yt-dlp. Metadata e playlist nuk do te shtohen dot kur ky opsion është i aktivizuar</string>
<string name="use_item_url_not_playlist_summary">Metadata e playlist nuk do te shtohen dot kur ky opsion është i aktivizuar</string>
<string name="recode_video">Rikodo Videon</string>
<string name="recode_video_summary">Rikodon skedarin e videos sipas formatit të cilësuar nga përdoruesi</string>
<string name="restore_info">Shtypni \"Rivendosni\" për të bashkuar të dhënat e ruajtura me të dhënat tuaja aktuale.

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">Користите AlarmManager уместо WorkManager за планирање</string>
<string name="use_alarm_manager_summary">Омогућите ово ако је WorkManager ограничен од стране продавца уређаја или заказани послови нису превише прецизни</string>
<string name="use_item_url_not_playlist">Користи URL адресу предмета уместо URL адресе плејлисте</string>
<string name="use_item_url_not_playlist_summary">Корисно за Libretube офлајн плејлисте које yt-dlp не препознаје. Метаподаци плејлисте неће бити уграђени, ако је ово омогућено</string>
<string name="use_item_url_not_playlist_summary">Метаподаци плејлисте неће бити уграђени, ако је ово омогућено</string>
<string name="recode_video">Рекодирај видео снимак</string>
<string name="recode_video_summary">Рекодира фајл видео снимка у наведени формат видео снимка</string>
<string name="daily">Дневно</string>

View file

@ -416,7 +416,7 @@
<string name="monthly">Aylık</string>
<string name="cleanup_leftover_downloads">Artık indirmeleri temizleme (iptal edildi, hatalı)</string>
<string name="use_item_url_not_playlist">Çalma Listesi URL\'si yerine Öge URL\'sini kullanın</string>
<string name="use_item_url_not_playlist_summary">yt-dlp tarafından tanınmayan Libretube çevrimdışı çalma listeleri için kullanışlıdır. Bu etkinleştirildiğinde çalma listesi meta verileri gömülmeyecektir</string>
<string name="use_item_url_not_playlist_summary">Bu etkinleştirildiğinde çalma listesi meta verileri gömülmeyecektir</string>
<string name="recode_video">Videoyu tekrar kodla</string>
<string name="recode_video_summary">Video dosyasını belirtilen video formatına yeniden kodlar</string>
<string name="continue_anyway">Her Neyse Devam Et</string>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">Використовуйте AlarmManager замість WorkManager для планування</string>
<string name="use_alarm_manager_summary">Увімкніть це, якщо WorkManager обмежено постачальником вашого пристрою або заплановані завдання не надто точні</string>
<string name="use_item_url_not_playlist">Використовуйте URL елемента замість URL списку відтворення</string>
<string name="use_item_url_not_playlist_summary">Корисно для списків відтворення Libretube без мережі, які не розпізнаються yt-dlp. Метадані списку відтворення не буде вбудовано, якщо увімкнено цей параметр</string>
<string name="use_item_url_not_playlist_summary">Метадані списку відтворення не буде вбудовано, якщо увімкнено цей параметр</string>
<string name="recode_video">Перекодувати відео</string>
<string name="recode_video_summary">Перекодує відеофайл у вказаний відеоформат</string>
<string name="daily">Щоденно</string>

View file

@ -416,7 +416,7 @@
<string name="use_alarm_manager">Sử dụng AlarmManager thay cho WorkManager trong phần Lên lịch để tải xuống</string>
<string name="use_alarm_manager_summary">Hãy bật nếu WorkManager bị hạn chế bởi Nhà cung cấp thiết bị hoặc các công việc không được lên lịch chính xác</string>
<string name="use_item_url_not_playlist">Dùng URL mục thay cho URL danh sách phát</string>
<string name="use_item_url_not_playlist_summary">Dùng cho danh sách phát ngoại tuyến Libretube không được yt-dlp nhận dạng. Nếu bật thì metadata của danh sách phát sẽ không được thêm vào tệp</string>
<string name="use_item_url_not_playlist_summary">Nếu bật thì metadata của danh sách phát sẽ không được thêm vào tệp</string>
<string name="recode_video_summary">Mã hóa tệp video thành định dạng được chỉ định</string>
<string name="recode_video">Mã hóa Video</string>
</resources>

View file

@ -409,7 +409,7 @@
<string name="use_alarm_manager">使用闹铃管理器而非工作管理器安排任务</string>
<string name="use_alarm_manager_summary">如果工作管理器被设备厂商限制或安排好时间的任务不太精确,请启用这个</string>
<string name="use_item_url_not_playlist">使用项目 URL 而非播放列表 URL</string>
<string name="use_item_url_not_playlist_summary">对于未被 yt-dlp 识别的 Libretube 离线播放列表有用。启用此选项后不会内嵌播放列表元数据</string>
<string name="use_item_url_not_playlist_summary">启用此选项后不会内嵌播放列表元数据</string>
<string name="recode_video_summary">重编码视频文件到指定的视频格式</string>
<string name="recode_video">重新编码视频</string>
<string name="daily">每天</string>

View file

@ -416,7 +416,7 @@
<string name="monthly">每月</string>
<string name="cleanup_leftover_downloads">清理殘留的下載(取消或錯誤的下載)</string>
<string name="use_item_url_not_playlist">使用項目 URL 而非播放清單 URL</string>
<string name="use_item_url_not_playlist_summary">此功能適用於 Libretube 離線播放清單,這些清單無法被 yt-dlp 認識。啟用此選項後,播放清單的中繼資料將不會嵌入</string>
<string name="use_item_url_not_playlist_summary">啟用此選項後,播放清單的中繼資料將不會嵌入</string>
<string name="recode_video">重新編碼影片</string>
<string name="recode_video_summary">將影片檔案重新編碼為指定的影片格式</string>
<string name="data_fetching_extractor_youtube">資料擷取提取器YouTube</string>

View file

@ -1495,6 +1495,11 @@
</string-array>
<string-array name="data_fetching_extractor_youtube">
<item>yt-dlp</item>
<item>NewPipe</item>
</string-array>
<string-array name="data_fetching_extractor_youtube_values">
<item>YT_DLP</item>
<item>NEWPIPE</item>
</string-array>

View file

@ -430,4 +430,7 @@
<string name="deleted">Deleted</string>
<string name="websites">Websites</string>
<string name="other_youtube_extractor_args">Other YouTube Extractor Arguments</string>
<string name="items">item(s)</string>
<string name="socket_timeout">Socket Timeout</string>
<string name="socket_timeout_description">Time to wait before giving up, in seconds</string>
</resources>

View file

@ -128,6 +128,12 @@
app:summary="@string/buffer_size_summary"
app:title="@string/buffer_size" />
<EditTextPreference
android:icon="@drawable/ic_clock"
app:key="socket_timeout"
android:summary="@string/socket_timeout_description"
app:title="@string/socket_timeout"/>
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"

View file

@ -87,7 +87,7 @@
<ListPreference
android:defaultValue="NEWPIPE"
android:entries="@array/data_fetching_extractor_youtube"
android:entryValues="@array/data_fetching_extractor_youtube"
android:entryValues="@array/data_fetching_extractor_youtube_values"
android:icon="@drawable/baseline_manage_search_24"
app:key="youtube_data_fetching_extractor"
app:useSimpleSummaryProvider="true"