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(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) { if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
val version = YoutubeDL.getInstance().version(context) 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}]", 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.Update
import androidx.room.Upsert import androidx.room.Upsert
import com.deniscerri.ytdl.database.models.DownloadItem 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.DownloadItemSimple
import com.deniscerri.ytdl.database.models.Format import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.repository.DownloadRepository
@ -25,8 +26,9 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Active'") @Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloads() : Flow<List<DownloadItem>> fun getActiveDownloads() : Flow<List<DownloadItem>>
@RewriteQueriesToDropUnusedColumns
@Query("SELECT * FROM downloads WHERE status = 'Processing'") @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)") @Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)")
fun getDownloadsCountFlow(statuses: List<String>) : Flow<Int> fun getDownloadsCountFlow(statuses: List<String>) : Flow<Int>
@ -181,13 +183,17 @@ interface DownloadDao {
suspend fun deleteSingleProcessing(id: Long) suspend fun deleteSingleProcessing(id: Long)
@Upsert @Upsert
suspend fun update(item: DownloadItem) suspend fun update(item: DownloadItem) : Long
@Transaction @Transaction
suspend fun updateAll(list: List<DownloadItem>){ suspend fun updateAll(list: List<DownloadItem>) : List<DownloadItem> {
val toReturn = mutableListOf<DownloadItem>()
list.forEach { list.forEach {
update(it) it.id = update(it)
toReturn.add(it)
} }
return toReturn
} }
@Query("UPDATE downloads set status=:status where id=:id") @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.Transaction
import androidx.room.Update import androidx.room.Update
import com.deniscerri.ytdl.database.models.LogItem import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @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) @Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: LogItem) 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 subsLanguages: String = "en.*,.*-orig",
var audioFormatIDs : ArrayList<String> = arrayListOf(), var audioFormatIDs : ArrayList<String> = arrayListOf(),
var removeAudio: Boolean = false, var removeAudio: Boolean = false,
var alsoDownloadAsAudio: Boolean = false var alsoDownloadAsAudio: Boolean = false,
var recodeVideo: Boolean = false
) : Parcelable ) : Parcelable

View file

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

View file

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

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

View file

@ -18,15 +18,17 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R 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.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.util.Extensions.loadThumbnail import com.deniscerri.ytdl.util.Extensions.loadThumbnail
import com.deniscerri.ytdl.util.Extensions.popup import com.deniscerri.ytdl.util.Extensions.popup
import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.InfoUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import java.util.Locale 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( AsyncDifferConfig.Builder(
DIFF_CALLBACK DIFF_CALLBACK
).build()) { ).build()) {
@ -111,7 +113,25 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
} }
val fileSize = card.findViewById<TextView>(R.id.file_size) 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 if (fileSizeReadable == "?") fileSize.visibility = View.GONE
else { else {
fileSize.text = fileSizeReadable fileSize.text = fileSizeReadable
@ -157,15 +177,15 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
} }
companion object { companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItemSimple> = object : DiffUtil.ItemCallback<DownloadItemSimple>() { private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItemConfigureMultiple> = object : DiffUtil.ItemCallback<DownloadItemConfigureMultiple>() {
override fun areItemsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean { override fun areItemsTheSame(oldItem: DownloadItemConfigureMultiple, newItem: DownloadItemConfigureMultiple): Boolean {
return oldItem.url == newItem.url 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 && return oldItem.title == newItem.title &&
oldItem.author == newItem.author &&
oldItem.type == newItem.type && oldItem.type == newItem.type &&
oldItem.videoPreferences == newItem.videoPreferences &&
oldItem.format == newItem.format && oldItem.format == newItem.format &&
oldItem.incognito == newItem.incognito oldItem.incognito == newItem.incognito
} }

View file

@ -16,6 +16,7 @@ import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView import android.widget.AutoCompleteTextView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -359,7 +360,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
fun updateSelectedAudioFormat(formatID: String){ 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 downloadItem.format = this
val formatCard = requireView().findViewById<MaterialCardView>(R.id.format_card_constraintLayout) val formatCard = requireView().findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, listOf()) 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.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem 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.models.Format
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
@ -161,7 +162,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
processingItemsCount = items.size processingItemsCount = items.size
count.text = "${processingItemsCount} ${getString(R.string.selected)}" count.text = "${processingItemsCount} ${getString(R.string.selected)}"
listAdapter.submitList(items) listAdapter.submitList(items)
updateFileSize(items.map { it2 -> it2.format.filesize })
updateFileSize(items)
if (items.isNotEmpty()){ if (items.isNotEmpty()){
if (items.all { it2 -> it2.type == items[0].type }) { if (items.all { it2 -> it2.type == items[0].type }) {
@ -445,7 +447,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.adjust_audio) bottomSheet.setContentView(R.layout.adjust_audio)
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!! 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){ val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads() downloadViewModel.getProcessingDownloads()
@ -526,7 +528,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.adjust_video) bottomSheet.setContentView(R.layout.adjust_video)
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!! 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){ val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads() downloadViewModel.getProcessingDownloads()
@ -586,6 +588,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
items.forEach { it.videoPreferences.removeAudio = checked } items.forEach { it.videoPreferences.removeAudio = checked }
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } } 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 = {}, alsoDownloadAsAudioClicked = {},
extraCommandsClicked = { extraCommandsClicked = {
val callback = object : ExtraCommandsListener { val callback = object : ExtraCommandsListener {
@ -645,14 +651,30 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading } bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
} }
private fun updateFileSize(items: List<Long>){ private fun updateFileSize(items: List<DownloadItemConfigureMultiple>){
if (items.all { it > 5L }){ val fileSizes = mutableListOf<Long>()
val size = FileUtil.convertFileSize(items.sum()) items.forEach {
if (size != "?"){ if (it.type == DownloadViewModel.Type.video){
filesize.visibility = View.VISIBLE if (it.format.filesize <= 5L) {
filesize.text = "${getString(R.string.file_size)}: >~ $size" 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{ }else{
filesize.visibility = View.GONE filesize.visibility = View.GONE
} }

View file

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

View file

@ -331,7 +331,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
downloadViewModel.getItemByID(itemID) downloadViewModel.getItemByID(itemID)
} }
downloadViewModel.deleteDownload(deletedItem.id) 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)) { .setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem) downloadViewModel.insert(deletedItem)
}.show() }.show()

View file

@ -334,7 +334,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
downloadViewModel.getItemByID(itemID) downloadViewModel.getItemByID(itemID)
} }
downloadViewModel.deleteDownload(deletedItem.id) 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)) { .setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem) downloadViewModel.insert(deletedItem)
}.show() }.show()

View file

@ -146,7 +146,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
downloadViewModel.getItemByID(id) downloadViewModel.getItemByID(id)
} }
val deleteDialog = MaterialAlertDialogBuilder(requireContext()) 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.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
item.status = DownloadRepository.Status.Cancelled.toString() item.status = DownloadRepository.Status.Cancelled.toString()
@ -154,7 +154,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
downloadViewModel.updateDownload(item) 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)) { .setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
downloadViewModel.deleteDownload(item.id) downloadViewModel.deleteDownload(item.id)

View file

@ -321,7 +321,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
downloadViewModel.getItemByID(itemID) downloadViewModel.getItemByID(itemID)
} }
downloadViewModel.deleteDownload(deletedItem.id) 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)) { .setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem) downloadViewModel.insert(deletedItem)
}.show() }.show()

View file

@ -336,7 +336,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
downloadViewModel.getItemByID(itemID) downloadViewModel.getItemByID(itemID)
} }
downloadViewModel.deleteDownload(deletedItem.id) 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)) { .setAction(getString(R.string.undo)) {
downloadViewModel.insert(deletedItem) downloadViewModel.insert(deletedItem)
}.show() }.show()

View file

@ -84,7 +84,9 @@ class DownloadLogFragment : Fragment() {
val clipboard: ClipboardManager = val clipboard: ClipboardManager =
mainActivity.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager mainActivity.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(content.text) 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") val id = arguments?.getLong("logID")

View file

@ -202,7 +202,7 @@ class InfoUtil(private val context: Context) {
Html.fromHtml(obj.getString("uploader").toString()).toString() Html.fromHtml(obj.getString("uploader").toString()).toString()
}catch (e: Exception){ }catch (e: Exception){
Html.fromHtml(obj.getString("uploaderName").toString()).toString() Html.fromHtml(obj.getString("uploaderName").toString()).toString()
} }.replace(" - Topic", "")
val duration = obj.getInt("duration").toStringDuration(Locale.US) val duration = obj.getInt("duration").toStringDuration(Locale.US)
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg" 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") val lang = sharedPreferences.getString("app_language", "en")
if (searchEngine == "ytsearch" && context.getStringArray(R.array.subtitle_langs).contains(lang)){ if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
request.addOption("--extractor-args", "youtube:lang=$lang") 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") request.addOption("--flat-playlist")
@ -708,7 +712,7 @@ class InfoUtil(private val context: Context) {
val title = jsonObject.getStringByAny("alt_title", "title", "webpage_url_basename") val title = jsonObject.getStringByAny("alt_title", "title", "webpage_url_basename")
if (title == "[Private video]" || title == "[Deleted video]") continue 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() var duration = jsonObject.getIntByAny("duration").toString()
if (duration != "-1"){ if (duration != "-1"){
duration = jsonObject.getInt("duration").toStringDuration(Locale.US) duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
@ -1179,6 +1183,15 @@ class InfoUtil(private val context: Context) {
if (sharedPreferences.getBoolean("write_description", false)){ if (sharedPreferences.getBoolean("write_description", false)){
request.addOption("--write-description") 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"){ if (sharedPreferences.getString("prevent_duplicate_downloads", "")!! == "download_archive"){
@ -1234,7 +1247,7 @@ class InfoUtil(private val context: Context) {
} }
if (abrSort.isNotBlank()){ if (abrSort.isNotBlank()){
formatSorting.append(",abr~${abrSort}") formatSorting.append(",abr:${abrSort}")
} }
if(ext.isNotBlank()){ if(ext.isNotBlank()){
@ -1254,15 +1267,15 @@ class InfoUtil(private val context: Context) {
if (embedMetadata){ if (embedMetadata){
request.addOption("--embed-metadata") 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", "%(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", "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()) { if (downloadItem.playlistTitle.isNotEmpty()) {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") 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 { } else {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
} }
@ -1320,8 +1333,14 @@ class InfoUtil(private val context: Context) {
supportedContainers.contains(outputContainer) supportedContainers.contains(outputContainer)
){ ){
cont = 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) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) { if (embedThumb) {
request.addOption("--embed-thumbnail") request.addOption("--embed-thumbnail")

View file

@ -163,7 +163,7 @@ object UiUtil {
var filesize = chosenFormat.filesize 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 { formatCard.findViewById<TextView>(R.id.file_size).apply {
text = FileUtil.convertFileSize(filesize) text = FileUtil.convertFileSize(filesize)
setOnClickListener { setOnClickListener {
@ -1130,29 +1130,18 @@ object UiUtil {
saveAutoSubtitlesClicked: (Boolean) -> Unit, saveAutoSubtitlesClicked: (Boolean) -> Unit,
subtitleLanguagesSet: (String) -> Unit, subtitleLanguagesSet: (String) -> Unit,
removeAudioClicked: (Boolean) -> Unit, removeAudioClicked: (Boolean) -> Unit,
recodeVideoClicked: (Boolean) -> Unit,
alsoDownloadAsAudioClicked: (Boolean) -> Unit, alsoDownloadAsAudioClicked: (Boolean) -> Unit,
extraCommandsClicked: () -> Unit extraCommandsClicked: () -> Unit
){ ){
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) 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) val addChapters = view.findViewById<Chip>(R.id.add_chapters)
addChapters!!.isChecked = items.all { it.videoPreferences.addChapters } addChapters!!.isChecked = items.all { it.videoPreferences.addChapters }
addChapters.setOnClickListener{ addChapters.setOnClickListener{
addChaptersClicked(addChapters.isChecked) addChaptersClicked(addChapters.isChecked)
} }
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters) val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
if(items.size == 1 && items[0].downloadSections.isNotBlank()){ if(items.size == 1 && items[0].downloadSections.isNotBlank()){
splitByChapters.isEnabled = false splitByChapters.isEnabled = false
@ -1183,6 +1172,111 @@ object UiUtil {
saveThumbnailClicked(saveThumbnail.isChecked) 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) val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
sponsorBlock.isEnabled = sharedPreferences.getBoolean("use_sponsorblock", true) sponsorBlock.isEnabled = sharedPreferences.getBoolean("use_sponsorblock", true)
sponsorBlock!!.setOnClickListener { 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) val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
if (sharedPreferences.getBoolean("use_extra_commands", false)){ if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE extraCommands.visibility = View.VISIBLE
@ -1849,7 +1871,7 @@ object UiUtil {
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
chips.forEach { chips.forEach {
it.isChecked = editText.text.contains(it.text) it.isChecked = editText.text == it.text
chipGroup!!.addView(it) chipGroup!!.addView(it)
} }

View file

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

View file

@ -96,6 +96,34 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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 <com.google.android.material.chip.Chip
android:id="@+id/cut" android:id="@+id/cut"
style="@style/Widget.Material3.Chip.Assist.Elevated" style="@style/Widget.Material3.Chip.Assist.Elevated"
@ -111,24 +139,10 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </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> </LinearLayout>

View file

@ -27,12 +27,12 @@
app:singleLine="true"> app:singleLine="true">
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/embed_subtitles" android:id="@+id/save_thumbnail"
style="@style/Widget.Material3.Chip.Filter.Elevated" style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="false" android:checked="false"
android:text="@string/embed_subtitles" /> android:text="@string/save_thumb" />
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/add_chapters" android:id="@+id/add_chapters"
@ -57,15 +57,44 @@
</HorizontalScrollView> </HorizontalScrollView>
<HorizontalScrollView <HorizontalScrollView
android:scrollbars="none"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true"> 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 <com.google.android.material.chip.Chip
android:id="@+id/adjust_audio" android:id="@+id/adjust_audio"
style="@style/Widget.Material3.Chip.Assist.Elevated" style="@style/Widget.Material3.Chip.Assist.Elevated"
@ -82,76 +111,14 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/remove_audio" android:id="@+id/recode_video"
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"
style="@style/Widget.Material3.Chip.Filter.Elevated" style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="false" 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> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </HorizontalScrollView>
@ -196,21 +163,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </HorizontalScrollView>
@ -227,7 +179,6 @@
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/extra_commands" android:id="@+id/extra_commands"
android:visibility="gone"
style="@style/Widget.Material3.Chip.Assist.Elevated" style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -241,6 +192,21 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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> </com.google.android.material.chip.ChipGroup>

View file

@ -86,7 +86,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/format_note"> app:layout_constraintTop_toBottomOf="@id/formatnote_formatid">
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -113,7 +113,6 @@
android:clickable="false" android:clickable="false"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center" android:gravity="center"
android:maxLength="10"
android:minWidth="30dp" android:minWidth="30dp"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:textColor="@color/white" android:textColor="@color/white"
@ -180,13 +179,19 @@
android:clickable="false" android:clickable="false"
android:ellipsize="end" android:ellipsize="end"
android:gravity="bottom|end" android:gravity="bottom|end"
android:maxLength="10" android:maxWidth="70dp"
android:layout_marginEnd="5dp" android:maxLines="2"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="@+id/format_note" app:layout_constraintBottom_toBottomOf="@+id/format_note"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="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> </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="day">Ditë</string>
<string name="month">Muaj</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="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="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="show_download_count">Shfaq numrat në faqen e rradhës së shkarkimeve</string>
<string name="also_download_audio">Shkarko Gjithashtu si Audio</string> <string name="also_download_audio">Shkarko Gjithashtu si Audio</string>
@ -397,5 +398,17 @@
<string name="location">Vendi</string> <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="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="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="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> </resources>

View file

@ -26,9 +26,9 @@
<item>mp4</item> <item>mp4</item>
<item>webm</item> <item>webm</item>
<item>mkv</item> <item>mkv</item>
<item>mov</item>
<item>avi</item> <item>avi</item>
<item>flv</item> <item>flv</item>
<item>mov</item>
</string-array> </string-array>
<string-array name="video_containers_values"> <string-array name="video_containers_values">
@ -36,9 +36,9 @@
<item>mp4</item> <item>mp4</item>
<item>webm</item> <item>webm</item>
<item>mkv</item> <item>mkv</item>
<item>mov</item>
<item>avi</item> <item>avi</item>
<item>flv</item> <item>flv</item>
<item>mov</item>
</string-array> </string-array>
<string-array name="video_formats"> <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_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">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="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> </resources>

View file

@ -180,6 +180,15 @@
app:useSimpleSummaryProvider="true" app:useSimpleSummaryProvider="true"
app:title="@string/video_format" /> 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 <ListPreference
android:defaultValue="" android:defaultValue=""
android:entries="@array/video_codec" 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