more changes

This commit is contained in:
zaednasr 2024-07-16 23:27:09 +02:00
parent 4eb6cce374
commit f4f527721c
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
31 changed files with 578 additions and 301 deletions

View file

@ -273,9 +273,14 @@ class MainActivity : BaseActivity() {
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
val version = YoutubeDL.getInstance().version(context)
Snackbar.make(findViewById(R.id.frame_layout),
val snack = Snackbar.make(findViewById(R.id.frame_layout),
this@MainActivity.getString(R.string.ytld_update_success) + " [${version}]",
Snackbar.LENGTH_LONG).show()
Snackbar.LENGTH_LONG)
if (navigationView is BottomNavigationView) {
snack.setAnchorView(navigationView)
}
snack.show()
}
}
}

View file

@ -11,6 +11,7 @@ import androidx.room.Transaction
import androidx.room.Update
import androidx.room.Upsert
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
import com.deniscerri.ytdl.database.models.DownloadItemSimple
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.repository.DownloadRepository
@ -25,8 +26,9 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloads() : Flow<List<DownloadItem>>
@RewriteQueriesToDropUnusedColumns
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
fun getProcessingDownloads() : Flow<List<DownloadItemSimple>>
fun getProcessingDownloads() : Flow<List<DownloadItemConfigureMultiple>>
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)")
fun getDownloadsCountFlow(statuses: List<String>) : Flow<Int>
@ -181,13 +183,17 @@ interface DownloadDao {
suspend fun deleteSingleProcessing(id: Long)
@Upsert
suspend fun update(item: DownloadItem)
suspend fun update(item: DownloadItem) : Long
@Transaction
suspend fun updateAll(list: List<DownloadItem>){
suspend fun updateAll(list: List<DownloadItem>) : List<DownloadItem> {
val toReturn = mutableListOf<DownloadItem>()
list.forEach {
update(it)
it.id = update(it)
toReturn.add(it)
}
return toReturn
}
@Query("UPDATE downloads set status=:status where id=:id")

View file

@ -7,6 +7,7 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
@Dao
@ -36,6 +37,16 @@ interface LogDao {
}
}
@Query("UPDATE logs set content=:l where id=:id")
suspend fun updateLogContent(l: String, id: Long)
@Transaction
suspend fun updateLog(line: String, id: Long) {
val l = getByID(id) ?: return
val log = l.content ?: ""
updateLogContent(log.appendLineToLog(line), id)
}
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: LogItem)

View file

@ -0,0 +1,24 @@
package com.deniscerri.ytdl.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
@Entity(tableName = "downloads")
data class DownloadItemConfigureMultiple(
@PrimaryKey(autoGenerate = true)
var id: Long,
var url: String,
var title: String,
var thumb: String,
var duration: String,
var format: Format,
var allFormats: MutableList<Format>,
var audioPreferences: AudioPreferences,
var videoPreferences: VideoPreferences,
@ColumnInfo(defaultValue = "Queued")
var status: String,
var type: DownloadViewModel.Type,
var incognito: Boolean = false
)

View file

@ -14,5 +14,6 @@ data class VideoPreferences (
var subsLanguages: String = "en.*,.*-orig",
var audioFormatIDs : ArrayList<String> = arrayListOf(),
var removeAudio: Boolean = false,
var alsoDownloadAsAudio: Boolean = false
var alsoDownloadAsAudio: Boolean = false,
var recodeVideo: Boolean = false
) : Parcelable

View file

@ -23,6 +23,7 @@ import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.dao.DownloadDao
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
import com.deniscerri.ytdl.database.models.DownloadItemSimple
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil
@ -43,7 +44,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
pagingSourceFactory = {downloadDao.getAllDownloads()}
)
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads().distinctUntilChanged()
val processingDownloads : Flow<List<DownloadItemSimple>> = downloadDao.getProcessingDownloads().distinctUntilChanged()
val processingDownloads : Flow<List<DownloadItemConfigureMultiple>> = downloadDao.getProcessingDownloads().distinctUntilChanged()
val queuedDownloads : Pager<Int, DownloadItemSimple> = Pager(
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {downloadDao.getQueuedDownloads()}
@ -74,7 +75,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
val scheduledDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Scheduled).toListString())
enum class Status {
Active, Queued, Error, Cancelled, Saved, Processing, Scheduled
Active, Queued, Error, Cancelled, Saved, Processing, Scheduled, Duplicate
}
suspend fun insert(item: DownloadItem) : Long {
@ -102,8 +103,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.update(item)
}
suspend fun updateAll(list: List<DownloadItem>) {
downloadDao.updateAll(list)
suspend fun updateAll(list: List<DownloadItem>) : List<DownloadItem> {
return downloadDao.updateAll(list)
}
suspend fun updateWithoutUpsert(item: DownloadItem){

View file

@ -39,12 +39,8 @@ class LogRepository(private val logDao: LogDao) {
}
suspend fun update(line: String, id: Long){
runCatching {
val item = getItem(id) ?: return
val log = item.content ?: ""
item.content = log.appendLineToLog(line)
//item.content += "\n$line"
logDao.update(item)
kotlin.runCatching {
logDao.updateLog(line, id)
}
}

View file

@ -27,6 +27,7 @@ import com.deniscerri.ytdl.database.dao.DownloadDao
import com.deniscerri.ytdl.database.models.AudioPreferences
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
import com.deniscerri.ytdl.database.models.DownloadItemSimple
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.HistoryItem
@ -68,7 +69,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val allDownloads : Flow<PagingData<DownloadItem>>
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
val activeDownloads : Flow<List<DownloadItem>>
val processingDownloads : Flow<List<DownloadItemSimple>>
val processingDownloads : Flow<List<DownloadItemConfigureMultiple>>
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
val savedDownloads : Flow<PagingData<DownloadItemSimple>>
@ -211,6 +212,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
val saveSubs = sharedPreferences.getBoolean("write_subtitles", false)
val saveAutoSubs = sharedPreferences.getBoolean("write_auto_subtitles", false)
val recodeVideo = sharedPreferences.getBoolean("recode_video", false)
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
@ -250,7 +252,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
ArrayList(sponsorblock),
saveSubs,
saveAutoSubs,
audioFormatIDs = preferredAudioFormats
audioFormatIDs = preferredAudioFormats,
recodeVideo = recodeVideo
)
val extraCommands = when(type){
@ -383,6 +386,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
val saveSubs = sharedPreferences.getBoolean("write_subtitles", false)
val saveAutoSubs = sharedPreferences.getBoolean("write_auto_subtitles", false)
val recodeVideo = sharedPreferences.getBoolean("recode_video", false)
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
@ -416,7 +420,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs)
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, recodeVideo = recodeVideo)
var path = defaultPath
historyItem.downloadPath.first().apply {
File(this).parent?.apply {
@ -789,43 +793,36 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
//CHECK DUPLICATES
var alreadyExists = false
var isDuplicate = false
if (checkDuplicate.isNotEmpty() && !ignoreDuplicates){
when(checkDuplicate){
"download_archive" -> {
if (downloadArchive.any { d -> it.url.contains(d) }){
alreadyExists = true
if (it.id == 0L) {
it.status = DownloadRepository.Status.Processing.toString()
isDuplicate = true
if (it.id == 0L){
val id = runBlocking {
repository.insert(it)
}
it.id = id
}
existingItemIDs.add(
AlreadyExistsIDs(
it.id,
null
)
)
it.status = DownloadRepository.Status.Duplicate.toString()
repository.update(it)
existingItemIDs.add(AlreadyExistsIDs(it.id,null))
}
}
"url_type" -> {
val existingDownload = activeAndQueuedDownloads.firstOrNull { a -> a.type == it.type && a.url == it.url }
if (existingDownload != null){
it.status = DownloadRepository.Status.Processing.toString()
val id = runBlocking {
repository.insert(it)
isDuplicate = true
if (it.id == 0L){
val id = runBlocking {
repository.insert(it)
}
it.id = id
}
it.id = id
alreadyExists = true
existingItemIDs.add(
AlreadyExistsIDs(
it.id,
null
)
)
it.status = DownloadRepository.Status.Duplicate.toString()
repository.update(it)
existingItemIDs.add(AlreadyExistsIDs(it.id,null))
}else{
//check if downloaded and file exists
val history = withContext(Dispatchers.IO){
@ -837,17 +834,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
if (existingHistoryItem != null){
alreadyExists = true
it.status = DownloadRepository.Status.Processing.toString()
val id = runBlocking {
repository.insert(it)
isDuplicate = true
if (it.id == 0L){
val id = runBlocking {
repository.insert(it)
}
it.id = id
}
existingItemIDs.add(
AlreadyExistsIDs(
id,
existingHistoryItem.id
)
)
it.status = DownloadRepository.Status.Duplicate.toString()
repository.update(it)
existingItemIDs.add(AlreadyExistsIDs(it.id,existingHistoryItem.id))
}
}
}
@ -863,12 +859,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
if (existingDownload != null){
it.status = DownloadRepository.Status.Processing.toString()
val id = runBlocking {
repository.insert(it)
isDuplicate = true
if (it.id == 0L){
val id = runBlocking {
repository.insert(it)
}
it.id = id
}
alreadyExists = true
existingItemIDs.add(AlreadyExistsIDs(id, null))
it.status = DownloadRepository.Status.Duplicate.toString()
repository.update(it)
existingItemIDs.add(AlreadyExistsIDs(it.id, null))
}else{
//check if downloaded and file exists
val history = withContext(Dispatchers.IO){
@ -880,31 +880,30 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
if (existingHistoryItem != null){
alreadyExists = true
it.status = DownloadRepository.Status.Processing.toString()
val id = runBlocking {
repository.insert(it)
isDuplicate = true
if (it.id == 0L){
val id = runBlocking {
repository.insert(it)
}
it.id = id
}
existingItemIDs.add(
AlreadyExistsIDs(
id,
existingHistoryItem.id
)
)
it.status = DownloadRepository.Status.Duplicate.toString()
repository.update(it)
existingItemIDs.add(AlreadyExistsIDs(it.id, existingHistoryItem.id))
}
}
}
}
}
if (!alreadyExists){
if (!isDuplicate){
queuedItems.add(it)
}
}
repository.updateAll(queuedItems)
val queued = repository.updateAll(queuedItems)
//if scheduler is on
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
@ -919,12 +918,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
}else{
if (!sharedPreferences.getBoolean("paused_downloads", false)) {
repository.startDownloadWorker(queuedItems, context)
repository.startDownloadWorker(queued, context)
}
if(!useScheduler){
CoroutineScope(Dispatchers.IO).launch {
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
queued.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
kotlin.runCatching {
resultRepository.updateDownloadItem(it)?.apply {
repository.updateWithoutUpsert(this)
@ -934,7 +933,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
}else{
CoroutineScope(Dispatchers.IO).launch {
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
queued.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
kotlin.runCatching {
resultRepository.updateDownloadItem(it)?.apply {
repository.updateWithoutUpsert(this)

View file

@ -60,6 +60,7 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob

View file

@ -18,15 +18,17 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItemSimple
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.util.Extensions.loadThumbnail
import com.deniscerri.ytdl.util.Extensions.popup
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.InfoUtil
import com.google.android.material.button.MaterialButton
import java.util.Locale
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItemSimple?, ConfigureMultipleDownloadsAdapter.ViewHolder>(
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItemConfigureMultiple?, ConfigureMultipleDownloadsAdapter.ViewHolder>(
AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
@ -111,7 +113,25 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
}
val fileSize = card.findViewById<TextView>(R.id.file_size)
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
val fileSizeReadable = if(item.type != DownloadViewModel.Type.video){
FileUtil.convertFileSize(item.format.filesize)
}else{
if (item.format.filesize < 10L) {
FileUtil.convertFileSize(0)
}else{
val preferredAudioFormatIDs = item.videoPreferences.audioFormatIDs
val audioFilesize = if (item.videoPreferences.removeAudio) {
0
}else{
item.allFormats
.filter { preferredAudioFormatIDs.contains(it.format_id) }
.sumOf { it.filesize }
}
FileUtil.convertFileSize(item.format.filesize + audioFilesize)
}
}
if (fileSizeReadable == "?") fileSize.visibility = View.GONE
else {
fileSize.text = fileSizeReadable
@ -157,15 +177,15 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItemSimple> = object : DiffUtil.ItemCallback<DownloadItemSimple>() {
override fun areItemsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItemConfigureMultiple> = object : DiffUtil.ItemCallback<DownloadItemConfigureMultiple>() {
override fun areItemsTheSame(oldItem: DownloadItemConfigureMultiple, newItem: DownloadItemConfigureMultiple): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
override fun areContentsTheSame(oldItem: DownloadItemConfigureMultiple, newItem: DownloadItemConfigureMultiple): Boolean {
return oldItem.title == newItem.title &&
oldItem.author == newItem.author &&
oldItem.type == newItem.type &&
oldItem.videoPreferences == newItem.videoPreferences &&
oldItem.format == newItem.format &&
oldItem.incognito == newItem.incognito
}

View file

@ -16,6 +16,7 @@ import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.ViewCompat
@ -359,7 +360,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
@SuppressLint("RestrictedApi")
fun updateSelectedAudioFormat(formatID: String){
resultItem?.formats?.find { it.format_id == formatID }?.apply {
val formats = (resultItem?.formats ?: listOf()) + genericAudioFormats
formats.find { it.format_id == formatID }?.apply {
downloadItem.format = this
val formatCard = requireView().findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, listOf())

View file

@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
@ -161,7 +162,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
processingItemsCount = items.size
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
listAdapter.submitList(items)
updateFileSize(items.map { it2 -> it2.format.filesize })
updateFileSize(items)
if (items.isNotEmpty()){
if (items.all { it2 -> it2.type == items[0].type }) {
@ -445,7 +447,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.adjust_audio)
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
sheetView.findViewById<View>(R.id.adjust).setPadding(10,padding,10,padding)
val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads()
@ -526,7 +528,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.adjust_video)
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
sheetView.findViewById<View>(R.id.adjust).setPadding(10,padding,10,padding)
val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads()
@ -586,6 +588,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
items.forEach { it.videoPreferences.removeAudio = checked }
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
},
recodeVideoClicked = {checked ->
items.forEach { it.videoPreferences.recodeVideo = checked }
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
},
alsoDownloadAsAudioClicked = {},
extraCommandsClicked = {
val callback = object : ExtraCommandsListener {
@ -645,14 +651,30 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
}
private fun updateFileSize(items: List<Long>){
if (items.all { it > 5L }){
val size = FileUtil.convertFileSize(items.sum())
if (size != "?"){
filesize.visibility = View.VISIBLE
filesize.text = "${getString(R.string.file_size)}: >~ $size"
private fun updateFileSize(items: List<DownloadItemConfigureMultiple>){
val fileSizes = mutableListOf<Long>()
items.forEach {
if (it.type == DownloadViewModel.Type.video){
if (it.format.filesize <= 5L) {
fileSizes.add(0)
}else{
val preferredAudioFormatIDs = it.videoPreferences.audioFormatIDs
val audioFormatSize = if (it.videoPreferences.removeAudio) {
0
}else{
it.allFormats
.filter { f -> preferredAudioFormatIDs.contains(f.format_id) }
.sumOf { f -> f.filesize }
}
fileSizes.add(it.format.filesize + audioFormatSize)
}
}
}
if (fileSizes.all { it > 5L }){
val size = FileUtil.convertFileSize(fileSizes.sum())
filesize.isVisible = size != "?"
filesize.text = "${getString(R.string.file_size)}: >~ $size"
}else{
filesize.visibility = View.GONE
}

View file

@ -15,6 +15,7 @@ import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.isVisible
@ -104,6 +105,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
thumb = resultItem!!.thumb
website = resultItem!!.website
url = resultItem!!.url
videoPreferences.apply {
audioFormatIDs = downloadViewModel.getPreferredAudioFormats(allFormats)
}
}
}else if (currentDownloadItem != null){
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
@ -376,6 +380,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
downloadItem.videoPreferences.removeAudio = it
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, if (it) listOf() else downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) })
},
recodeVideoClicked = {
downloadItem.videoPreferences.recodeVideo = it
},
alsoDownloadAsAudioClicked = {
downloadItem.videoPreferences.alsoDownloadAsAudio = it
},
@ -416,7 +423,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
@SuppressLint("RestrictedApi")
fun updateSelectedAudioFormat(format: Format){
if (genericAudioFormats.contains(format)) {
if (downloadItem.videoPreferences.audioFormatIDs.contains(format.format_id)) {
return
}

View file

@ -331,7 +331,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, Snackbar.LENGTH_LONG)
Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -334,7 +334,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, Snackbar.LENGTH_LONG)
Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -146,7 +146,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
downloadViewModel.getItemByID(id)
}
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title.ifEmpty { item.url } + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
item.status = DownloadRepository.Status.Cancelled.toString()
@ -154,7 +154,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
downloadViewModel.updateDownload(item)
}
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title.ifEmpty { item.url }, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO) {
downloadViewModel.deleteDownload(item.id)

View file

@ -321,7 +321,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, Snackbar.LENGTH_LONG)
Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -336,7 +336,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, Snackbar.LENGTH_LONG)
Snackbar.make(scheduledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem)
}.show()

View file

@ -84,7 +84,9 @@ class DownloadLogFragment : Fragment() {
val clipboard: ClipboardManager =
mainActivity.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(content.text)
Snackbar.make(bottomAppBar, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
Snackbar.make(bottomAppBar, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG)
.setAnchorView(bottomAppBar)
.show()
}
val id = arguments?.getLong("logID")

View file

@ -202,7 +202,7 @@ class InfoUtil(private val context: Context) {
Html.fromHtml(obj.getString("uploader").toString()).toString()
}catch (e: Exception){
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
}
}.replace(" - Topic", "")
val duration = obj.getInt("duration").toStringDuration(Locale.US)
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
@ -658,8 +658,12 @@ class InfoUtil(private val context: Context) {
}
}
val lang = sharedPreferences.getString("app_language", "en")
if (searchEngine == "ytsearch" && context.getStringArray(R.array.subtitle_langs).contains(lang)){
request.addOption("--extractor-args", "youtube:lang=$lang")
if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
var extractorArgs = "player_client=default,mediaconnect,android"
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang"
}
request.addOption("--extractor-args", "youtube:$extractorArgs")
}
request.addOption("--flat-playlist")
@ -708,7 +712,7 @@ class InfoUtil(private val context: Context) {
val title = jsonObject.getStringByAny("alt_title", "title", "webpage_url_basename")
if (title == "[Private video]" || title == "[Deleted video]") continue
var author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
var author = jsonObject.getStringByAny("artist", "uploader", "channel", "playlist_uploader", "uploader_id")
var duration = jsonObject.getIntByAny("duration").toString()
if (duration != "-1"){
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
@ -1179,6 +1183,15 @@ class InfoUtil(private val context: Context) {
if (sharedPreferences.getBoolean("write_description", false)){
request.addOption("--write-description")
}
if (downloadItem.url.isYoutubeURL()) {
var extractorArgs = "player_client=default,mediaconnect,android"
val lang = sharedPreferences.getString("app_language", "en")
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang"
}
request.addOption("--extractor-args", "youtube:$extractorArgs")
}
}
if (sharedPreferences.getString("prevent_duplicate_downloads", "")!! == "download_archive"){
@ -1234,7 +1247,7 @@ class InfoUtil(private val context: Context) {
}
if (abrSort.isNotBlank()){
formatSorting.append(",abr~${abrSort}")
formatSorting.append(",abr:${abrSort}")
}
if(ext.isNotBlank()){
@ -1254,15 +1267,15 @@ class InfoUtil(private val context: Context) {
if (embedMetadata){
request.addOption("--embed-metadata")
request.addOption("--parse-metadata", "%(artist,uploader)s:^(?P<meta_album_artist>[^,]*)")
request.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)")
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist|)s:%(album_artist)s")
request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\\d+)")
if (downloadItem.playlistTitle.isNotEmpty()) {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(track_number)s")
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)")
} else {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
}
@ -1320,8 +1333,14 @@ class InfoUtil(private val context: Context) {
supportedContainers.contains(outputContainer)
){
cont = outputContainer
request.addOption("--merge-output-format", outputContainer.lowercase())
if (outputContainer != "webm") {
if (downloadItem.videoPreferences.recodeVideo) {
request.addOption("--recode-video", outputContainer.lowercase())
}else{
request.addOption("--merge-output-format", outputContainer.lowercase())
}
if (!listOf("webm", "avi", "flv").contains(outputContainer.lowercase())) {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) {
request.addOption("--embed-thumbnail")

View file

@ -163,7 +163,7 @@ object UiUtil {
var filesize = chosenFormat.filesize
if (!audioFormats.isNullOrEmpty()) filesize += audioFormats.sumOf { it.filesize }
if (!audioFormats.isNullOrEmpty() && filesize > 10L) filesize += audioFormats.sumOf { it.filesize }
formatCard.findViewById<TextView>(R.id.file_size).apply {
text = FileUtil.convertFileSize(filesize)
setOnClickListener {
@ -1130,29 +1130,18 @@ object UiUtil {
saveAutoSubtitlesClicked: (Boolean) -> Unit,
subtitleLanguagesSet: (String) -> Unit,
removeAudioClicked: (Boolean) -> Unit,
recodeVideoClicked: (Boolean) -> Unit,
alsoDownloadAsAudioClicked: (Boolean) -> Unit,
extraCommandsClicked: () -> Unit
){
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
val saveAutoSubtitles = view.findViewById<Chip>(R.id.save_auto_subtitles)
val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages)
embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs }
embedSubs.setOnClickListener {
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
embedSubsClicked(embedSubs.isChecked)
}
val addChapters = view.findViewById<Chip>(R.id.add_chapters)
addChapters!!.isChecked = items.all { it.videoPreferences.addChapters }
addChapters.setOnClickListener{
addChaptersClicked(addChapters.isChecked)
}
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
if(items.size == 1 && items[0].downloadSections.isNotBlank()){
splitByChapters.isEnabled = false
@ -1183,6 +1172,111 @@ object UiUtil {
saveThumbnailClicked(saveThumbnail.isChecked)
}
val adjustSubtitles = view.findViewById<Chip>(R.id.adjust_subtitles)
adjustSubtitles.setOnClickListener {
val adjustSubtitleView = context.layoutInflater.inflate(R.layout.subtitle_download_preferences_dialog, null)
val embedSubs = adjustSubtitleView.findViewById<MaterialSwitch>(R.id.embed_subtitles)
val saveSubtitles = adjustSubtitleView.findViewById<MaterialSwitch>(R.id.save_subs)
val saveAutoSubtitles = adjustSubtitleView.findViewById<MaterialSwitch>(R.id.save_auto_subs)
val subtitleLanguages = adjustSubtitleView.findViewById<ConstraintLayout>(R.id.subtitle_languages)
val subtitleLanguagesDescription = adjustSubtitleView.findViewById<TextView>(R.id.subtitle)
subtitleLanguagesDescription.text = items.first().videoPreferences.subsLanguages
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked
embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs }
embedSubs.setOnClickListener {
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked
embedSubsClicked(embedSubs.isChecked)
}
if (items.all { it.videoPreferences.writeSubs}) {
saveSubtitles.isChecked = true
subtitleLanguages.visibility = View.VISIBLE
}
if (items.all { it.videoPreferences.writeAutoSubs}) {
saveAutoSubtitles.isChecked = true
subtitleLanguages.visibility = View.VISIBLE
}
saveSubtitles.setOnCheckedChangeListener { _, _ ->
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
saveSubtitlesClicked(saveSubtitles.isChecked)
}
saveAutoSubtitles.setOnCheckedChangeListener { _, _ ->
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
saveAutoSubtitlesClicked(saveAutoSubtitles.isChecked)
}
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked
subtitleLanguages.setOnClickListener {
val currentSubtitleLang = if (items.size == 1 || items.all { it.videoPreferences.subsLanguages == items[0].videoPreferences.subsLanguages }){
items[0].videoPreferences.subsLanguages
}else {
""
}.ifEmpty { sharedPreferences.getString("subs_lang", "en.*,.*-orig")!! }
showSubtitleLanguagesDialog(context, currentSubtitleLang){
subtitleLanguagesSet(it)
subtitleLanguagesDescription.text = it
}
}
val adjustSubtitleDialog = MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.subtitles))
.setView(adjustSubtitleView)
.setIcon(R.drawable.ic_subtitles)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
adjustSubtitleDialog.show()
}
if (items.size == 1 && items.first().id == 0L){
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
adjustAudio.setOnClickListener {
val adjustAudioView = context.layoutInflater.inflate(R.layout.audio_download_preferences_dialog, null)
adjustAudioView.findViewById<MaterialSwitch>(R.id.remove_audio).apply {
isChecked = items.first().videoPreferences.removeAudio
setOnCheckedChangeListener { _, b ->
removeAudioClicked(b)
}
}
adjustAudioView.findViewById<MaterialSwitch>(R.id.also_download_audio).apply {
isChecked = items.first().videoPreferences.alsoDownloadAsAudio
setOnCheckedChangeListener { _, b ->
alsoDownloadAsAudioClicked(b)
}
}
val adjustAudioDialog = MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.audio))
.setView(adjustAudioView)
.setIcon(R.drawable.ic_music)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
adjustAudioDialog.show()
}
}else{
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
adjustAudio.isVisible = false
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
removeAudio.isVisible = true
removeAudio.isChecked = items.all { it.videoPreferences.removeAudio }
removeAudio.setOnCheckedChangeListener { _, _ ->
removeAudioClicked(removeAudio.isChecked)
}
}
val recodeVideo = view.findViewById<Chip>(R.id.recode_video)
recodeVideo.isChecked = items.all { it.videoPreferences.recodeVideo }
recodeVideo.setOnCheckedChangeListener { _, _ ->
recodeVideoClicked(recodeVideo.isChecked)
}
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
sponsorBlock.isEnabled = sharedPreferences.getBoolean("use_sponsorblock", true)
sponsorBlock!!.setOnClickListener {
@ -1284,78 +1378,6 @@ object UiUtil {
}
}
items.forEach { it.videoPreferences.subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!! }
if (items.all { it.videoPreferences.writeSubs}) {
saveSubtitles.isChecked = true
subtitleLanguages.visibility = View.VISIBLE
}
if (items.all { it.videoPreferences.writeAutoSubs}) {
saveAutoSubtitles.isChecked = true
subtitleLanguages.visibility = View.VISIBLE
}
saveSubtitles.setOnCheckedChangeListener { _, _ ->
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
saveSubtitlesClicked(saveSubtitles.isChecked)
}
saveAutoSubtitles.setOnCheckedChangeListener { _, _ ->
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
saveAutoSubtitlesClicked(saveAutoSubtitles.isChecked)
}
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
subtitleLanguages.setOnClickListener {
val currentSubtitleLang = if (items.size == 1 || items.all { it.videoPreferences.subsLanguages == items[0].videoPreferences.subsLanguages }){
items[0].videoPreferences.subsLanguages
}else {
""
}
showSubtitleLanguagesDialog(context, currentSubtitleLang){
subtitleLanguagesSet(it)
}
}
if (items.size == 1 && items.first().id == 0L){
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
adjustAudio.setOnClickListener {
val adjustAudioView = context.layoutInflater.inflate(R.layout.audio_download_preferences_dialog, null)
adjustAudioView.findViewById<MaterialSwitch>(R.id.remove_audio).apply {
isChecked = items.first().videoPreferences.removeAudio
setOnCheckedChangeListener { _, b ->
removeAudioClicked(b)
}
}
adjustAudioView.findViewById<MaterialSwitch>(R.id.also_download_audio).apply {
isChecked = items.first().videoPreferences.alsoDownloadAsAudio
setOnCheckedChangeListener { _, b ->
alsoDownloadAsAudioClicked(b)
}
}
val adjustAudioDialog = MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.adjust_audio))
.setView(adjustAudioView)
.setIcon(R.drawable.ic_music)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
adjustAudioDialog.show()
}
}else{
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
adjustAudio.isVisible = false
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
removeAudio.isVisible = true
removeAudio.isChecked = items.all { it.videoPreferences.removeAudio }
removeAudio.setOnCheckedChangeListener { _, _ ->
removeAudioClicked(removeAudio.isChecked)
}
}
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE
@ -1849,7 +1871,7 @@ object UiUtil {
withContext(Dispatchers.Main){
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
chips.forEach {
it.isChecked = editText.text.contains(it.text)
it.isChecked = editText.text == it.text
chipGroup!!.addView(it)
}

View file

@ -179,7 +179,9 @@ class DownloadWorker(
}
}
}.onSuccess {
resultRepo.updateDownloadItem(downloadItem)
resultRepo.updateDownloadItem(downloadItem)?.apply {
dao.updateWithoutUpsert(this)
}
val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking {
var finalPaths : MutableList<String>?

View file

@ -96,6 +96,34 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="match_parent">
<com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content"
android:layout_height="match_parent">
<com.google.android.material.chip.Chip
android:id="@+id/extra_commands"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/use_extra_commands"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_terminal"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/cut"
style="@style/Widget.Material3.Chip.Assist.Elevated"
@ -111,24 +139,10 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<com.google.android.material.chip.Chip
android:id="@+id/extra_commands"
android:visibility="gone"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/use_extra_commands"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_terminal"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>

View file

@ -27,12 +27,12 @@
app:singleLine="true">
<com.google.android.material.chip.Chip
android:id="@+id/embed_subtitles"
android:id="@+id/save_thumbnail"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/embed_subtitles" />
android:text="@string/save_thumb" />
<com.google.android.material.chip.Chip
android:id="@+id/add_chapters"
@ -57,15 +57,44 @@
</HorizontalScrollView>
<HorizontalScrollView
android:scrollbars="none"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true">
<com.google.android.material.chip.Chip
android:id="@+id/adjust_subtitles"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/subtitles"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_subtitles"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/remove_audio"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minWidth="30dp"
android:text="@string/remove_audio"
android:visibility="gone"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/adjust_audio"
style="@style/Widget.Material3.Chip.Assist.Elevated"
@ -82,76 +111,14 @@
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/remove_audio"
android:visibility="gone"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/remove_audio"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/save_thumbnail"
android:id="@+id/recode_video"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/save_thumb" />
android:text="@string/recode_video" />
<com.google.android.material.chip.Chip
android:id="@+id/save_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/save_subs"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:scrollbars="none"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/save_auto_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/save_auto_subs"/>
<com.google.android.material.chip.Chip
android:id="@+id/subtitle_languages"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:checked="false"
android:text="@string/subtitle_languages"/>
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
@ -196,21 +163,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/cut"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_cut"
android:minWidth="30dp"
android:text="@string/cut"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
@ -227,7 +179,6 @@
<com.google.android.material.chip.Chip
android:id="@+id/extra_commands"
android:visibility="gone"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -241,6 +192,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/cut"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_cut"
android:minWidth="30dp"
android:text="@string/cut"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup>

View file

@ -86,7 +86,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/format_note">
app:layout_constraintTop_toBottomOf="@id/formatnote_formatid">
<HorizontalScrollView
android:layout_width="wrap_content"
@ -113,7 +113,6 @@
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="10"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textColor="@color/white"
@ -180,13 +179,19 @@
android:clickable="false"
android:ellipsize="end"
android:gravity="bottom|end"
android:maxLength="10"
android:layout_marginEnd="5dp"
app:cornerRadius="10dp"
android:maxWidth="70dp"
android:maxLines="2"
app:layout_constraintBottom_toBottomOf="@+id/format_note"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/formatnote_formatid"
app:barrierDirection="bottom"
app:constraint_referenced_ids="format_note, format_id" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_height="wrap_content">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/embed_subtitles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="10dp"
android:text="@string/embed_subtitles" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/save_subs"
android:layout_width="match_parent"
android:layout_marginHorizontal="20dp"
android:text="@string/save_subs"
android:layout_height="wrap_content" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/save_auto_subs"
android:layout_width="match_parent"
android:layout_marginHorizontal="20dp"
android:text="@string/save_auto_subs"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:id="@+id/subtitle_languages"
android:layout_marginHorizontal="20dp">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textColor="?android:attr/textColorPrimaryDisableOnly"
android:text="@string/subtitle_languages"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View file

@ -358,6 +358,7 @@
<string name="day">Ditë</string>
<string name="month">Muaj</string>
<string name="archive">Arkiva e Shkarkimeve [Përdor skedarin e arkivës të yt-dlp për të parandaluar shkarkimet duplicate]</string>
<string name="disabled">Fikur</string>
<string name="swipe_gestures_download_card">Gjestet e rrëshqitjes në kartën e shkarkimit</string>
<string name="show_download_count">Shfaq numrat në faqen e rradhës së shkarkimeve</string>
<string name="also_download_audio">Shkarko Gjithashtu si Audio</string>
@ -397,5 +398,17 @@
<string name="location">Vendi</string>
<string name="enable_alarm_permission">Duhet të aktivizoni lejen SCHEDULE_EXACT_ALARM në cilësimet e aplikacionit.</string>
<string name="process_downloads_background">Shkarkimet janë ende duke u ngarkuar. Vazhdoni t\'i përpunoni ato në sfond dhe të filloni shkarkimin?</string>
<string name="navigation_bar">Navigimi</string>
<string name="label_visibility">Pamja e Titujve në Navigim</string>
<string name="always">Gjithmonë</string>
<string name="sync_with_source">Sinkronizo Me Burimin</string>
<string name="queue">Rradha</string>
<string name="reset">Rivendos</string>
<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="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">Shtyp \'Rikthe\' për të kombinuar të dhënat e ruajtura me të dhënat aktuale. Shtyp \'Rivendos\' për të fshirë gjithçka dhe vetëm përdor të dhënat e ruajtura në skedar.</string>
</resources>

View file

@ -26,9 +26,9 @@
<item>mp4</item>
<item>webm</item>
<item>mkv</item>
<item>mov</item>
<item>avi</item>
<item>flv</item>
<item>mov</item>
</string-array>
<string-array name="video_containers_values">
@ -36,9 +36,9 @@
<item>mp4</item>
<item>webm</item>
<item>mkv</item>
<item>mov</item>
<item>avi</item>
<item>flv</item>
<item>mov</item>
</string-array>
<string-array name="video_formats">

View file

@ -412,4 +412,6 @@
<string name="use_alarm_manager_summary">Enable this if WorkManager is restricted by your device vendor or scheduled jobs are not too precise</string>
<string name="use_item_url_not_playlist">Use Item URL instead of Playlist URL</string>
<string name="use_item_url_not_playlist_summary">Useful for Libretube offline playlists that are not recognised by yt-dlp. Playlist metadata wont be embedded with this enabled</string>
<string name="recode_video">Recode Video</string>
<string name="recode_video_summary">Recodes the video file to the specified video format</string>
</resources>

View file

@ -180,6 +180,15 @@
app:useSimpleSummaryProvider="true"
app:title="@string/video_format" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/baseline_video_metadata"
app:key="recode_video"
android:summary="@string/recode_video_summary"
app:title="@string/recode_video" />
<ListPreference
android:defaultValue=""
android:entries="@array/video_codec"

View file

@ -0,0 +1,66 @@
TEMPORARY just to save changelog
List is empty.
kotlin.collections.CollectionsKt.first(SourceFile:2)
com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.checkIfAllProcessingItemsHaveSameType(Unknown Source:21)
com.deniscerri.ytdl.ui.downloadcard.DownloadMultipleBottomSheetDialog$setupDialog$9$3$res$1.invokeSuspend(SourceFile:18)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:9)
kotlinx.coroutines.DispatchedTask.run(Unknown Source:96)
androidx.work.Worker$2.run(SourceFile:39)
kotlinx.coroutines.scheduling.TaskImpl.run(Unknown Source:2)
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(SourceFile:96)
add option in download settings to use alarm manager instead of workmanager for scheduled tasks
fix audio format importance
When updating formats for multiple items, now the app remembers what items dont need new formats and skips them
Move format source from the bottom of the format list to the filter bottom sheet
Added ability for the app to update the result items formats when you update the download item formats if there are any results with the same url
moved queue screen back in its separate tab due to performance issues
format updating in the multiple download card now applies updates immediately on each item
Update formats in background review. Also prevent from showing up every time it updates. Also delete saved after opening items n the multiple download card lol wtf. Also the notification. Change the title of notification to
Can that other one under the Copy log be fixed, too?snackbar
Bulk download shows size without audio fixed
When selecting audio format add generic last
Update ui crashes app from share activity
add option to use video url instead of playlist url to avoid piped private playlist issue
add write uri permission when opening files
fix app still updating formats even though the user closed the format bottom sheet
[BUG] Video title doesn't change in the download card #526
fixed app fetching player urls and chapters from yt-dlp
added ability to change theme without restarting the app. If the icon changes then the app will restart
prevent avi and flv containers from embedding thumbnails
Fixed Subs language preference isn't saved in the download card
add player_client=default,mediaconnect,android extractor args when data fetching and downloading to show more formats
Turned some metadata into digits
-> --parse-metadata "%(track_number,playlist_index)d:(?P<track_number>\d+)" --parse-metadata "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\d+)"
Removed NA from meta album artist --parse-metadata "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)"
Add --recode-video toggle in the settings to use instead of --merge-output-format
adjusted the "Adjust video" section in the download card and put all subtitle items together
Fixed app not selecting an audio format to show in the format view in the download card after updating formats