some changes

- fixed log removing some lines
- added MASTER channel in yt-dlp updates
- made errored downloads as a separate notification channel
- fix notification language conflict for portugal brasil in worker notification
- kept state of download card when going in landscape, even while updating data
- add crop thumbnail to adjust audio preferences
- fix command templates creation showing extra command checkboxes even though extra command is disabled
- fix preferred audio codec disrupting preferred audio id
This commit is contained in:
zaednasr 2023-11-19 16:31:00 +01:00
parent 6eeb662a2c
commit 3e4d89577a
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
26 changed files with 316 additions and 170 deletions

View file

@ -10,8 +10,8 @@ plugins {
def properties = new Properties()
def versionMajor = 1
def versionMinor = 6
def versionPatch = 8
def versionBuild = 1 // bump for dogfood builds, public betas, etc.
def versionPatch = 9
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def versionExt = ""
if (versionBuild > 0){

View file

@ -7,20 +7,22 @@ import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.WindowInsets
import android.widget.CheckBox
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.forEach
@ -30,7 +32,6 @@ import androidx.fragment.app.FragmentContainerView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
@ -42,7 +43,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.ui.HomeFragment
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil

View file

@ -6,6 +6,7 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class AudioPreferences(
var embedThumb: Boolean = true,
var cropThumb: Boolean? = null,
var splitByChapters: Boolean = false,
var sponsorBlockFilters: ArrayList<String> = arrayListOf()
) : Parcelable

View file

@ -41,25 +41,17 @@ class LogRepository(private val logDao: LogDao) {
suspend fun update(line: String, id: Long){
runCatching {
val item = getItem(id)
val log = item.content
//clean duplicate progress + add newline
val lines = log.split("\n").toMutableList()
run loop@ {
for(i in lines.size - 1 downTo 0){
val l = lines[i]
if(l.contains("\\[download]( *?)(\\d)(.*?)".toRegex())){
lines[i] = ""
return@loop
}
}
val item = getItem(id) ?: return
val log = item.content ?: ""
val lines = log.split("\n")
//clean dublicate progress + add newline
var newLine = line
if (newLine.contains("[download")){
newLine = "[download]" + line.split("[download]").last()
}
val l = if (line.contains("[download]")) {
"[download]" + line.split("[download]").last()
}else {
line
}
item.content = lines.filter { it.isNotBlank() }.joinToString("\n") + "\n${l}"
val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
item.content = l
logDao.update(item)
}
}

View file

@ -233,6 +233,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
var type = getDownloadType(givenType, resultItem.url)
if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video
@ -257,7 +258,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val preferredAudioFormats = arrayListOf<String>()
for (f in resultItem.formats.sortedBy { it.format_id }){
@ -391,6 +392,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
val customFileNameTemplate = when(historyItem.type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s")
@ -418,7 +421,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
else -> ""
}
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs)
val downloadPath = File(historyItem.downloadPath)
val path = if (downloadPath.exists()) downloadPath.parent else defaultPath
@ -625,6 +628,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
alarmScheduler.schedule()
}
if (items.any { it.playlistTitle.isEmpty() } && items.size > 1){
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
}
items.forEach {
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
@ -679,14 +686,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
}
if (items.any { it.playlistTitle.isEmpty() }){
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
withContext(Dispatchers.IO){
dao.updateMultiple(items)
}
}
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){
startDownloadWorker(queuedItems)
if(!useScheduler){

View file

@ -29,6 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.regex.Pattern
@ -195,50 +196,58 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
suspend fun updateItemData(res: ResultItem){
if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
kotlin.runCatching {
updatingData.emit(true)
val result = parseQueries(listOf(res.url))
updatingData.emit(false)
updateResultData.emit(result)
}.onFailure {
updatingData.emit(true)
val result = parseQueries(listOf(res.url))
updatingData.emit(false)
updateResultData.emit(result)
}
}
updateResultDataJob?.start()
updateResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingData.emit(false)
updateResultData.emit(mutableListOf())
}
}
}
updateResultDataJob?.start()
}
suspend fun cancelUpdateItemData(){
updateResultDataJob?.cancel()
updatingData.emit(false)
updateResultData.emit(null)
updateResultDataJob?.cancel()
}
suspend fun cancelUpdateFormatsItemData(){
updateFormatsResultDataJob?.cancel()
updatingFormats.emit(false)
updateFormatsResultData.emit(null)
updateFormatsResultDataJob?.cancel()
}
suspend fun updateFormatItemData(result: ResultItem){
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
kotlin.runCatching {
if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) {
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(true)
val formats = infoUtil.getFormats(result.url)
updatingFormats.emit(false)
if (formats.isNotEmpty()){
if (formats.isNotEmpty() && isActive) {
val res = getItemByURL(result.url)
res.formats = formats.toMutableList()
update(res)
}
updateFormatsResultData.emit(formats.toMutableList())
}.onFailure {
updatingFormats.emit(false)
updateFormatsResultData.emit(mutableListOf())
}
updateFormatsResultDataJob?.start()
updateFormatsResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(false)
updateFormatsResultData.emit(mutableListOf())
}
}
}
}
updateFormatsResultDataJob?.start()
}
}

View file

@ -33,10 +33,13 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
@ -258,6 +261,9 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
embedThumbClicked = {
downloadItem.audioPreferences.embedThumb = it
},
cropThumbClicked = {
downloadItem.audioPreferences.cropThumb = it
},
splitByChaptersClicked = {
downloadItem.audioPreferences.splitByChapters = it
},

View file

@ -50,6 +50,7 @@ import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -90,6 +91,33 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
infoUtil = InfoUtil(requireContext())
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val res: ResultItem?
val dwl: DownloadItem?
if (Build.VERSION.SDK_INT >= 33){
res = arguments?.getParcelable("result", ResultItem::class.java)
dwl = arguments?.getParcelable("downloadItem", DownloadItem::class.java)
}else{
res = arguments?.getParcelable<ResultItem>("result")
dwl = arguments?.getParcelable<DownloadItem>("downloadItem")
}
type = arguments?.getSerializable("type") as Type
if (res == null){
dismiss()
return
}
result = res
currentDownloadItem = dwl
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val downloadItem = getDownloadItem()
arguments?.putParcelable("result", result)
arguments?.putParcelable("downloadItem", downloadItem)
arguments?.putSerializable("type", downloadItem.type)
}
@SuppressLint("RestrictedApi", "InflateParams")
@ -98,26 +126,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
dialog.setContentView(view)
if (Build.VERSION.SDK_INT >= 33){
arguments?.getParcelable("result", ResultItem::class.java)
}else{
arguments?.getParcelable<ResultItem>("result")
}.apply {
if (this == null){
dismiss()
return
}else{
result = this
}
}
type = arguments?.getSerializable("type") as Type
currentDownloadItem = if (Build.VERSION.SDK_INT >= 33){
arguments?.getParcelable("downloadItem", DownloadItem::class.java)
}else{
arguments?.getParcelable<DownloadItem>("downloadItem")
}
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
@ -297,16 +305,18 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
download!!.setOnClickListener {
lifecycleScope.launch {
resultViewModel.cancelUpdateItemData()
resultViewModel.cancelUpdateFormatsItemData()
withContext(Dispatchers.IO){
resultViewModel.cancelUpdateItemData()
resultViewModel.cancelUpdateFormatsItemData()
}
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem()
runBlocking {
downloadViewModel.queueDownloads(listOf(item))
}
dismiss()
}
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem()
runBlocking {
downloadViewModel.queueDownloads(listOf(item))
}
dismiss()
}
download.setOnLongClickListener {
@ -371,7 +381,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
}
CoroutineScope(Dispatchers.IO).launch{
lifecycleScope.launch{
downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) {
withContext(Dispatchers.Main){
@ -427,6 +437,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
lifecycleScope.launch {
resultViewModel.updatingFormats.collectLatest {
if (it){
delay(500)
runCatching {
val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
@ -528,6 +539,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
return when(selectedTabPosition){
0 -> {

View file

@ -412,6 +412,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
it.audioPreferences.embedThumb = enabled
}
},
cropThumbClicked = {enabled ->
items.forEach {
it.audioPreferences.cropThumb = enabled
}
},
splitByChaptersClicked = {enabled ->
items.forEach {
it.audioPreferences.splitByChapters = enabled

View file

@ -34,12 +34,15 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.android.material.textfield.TextInputLayout.EndIconMode
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File

View file

@ -31,6 +31,7 @@ import androidx.core.os.bundleOf
import androidx.core.view.forEach
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
@ -57,7 +58,11 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/**
* A fragment representing a list of Items.
@ -172,8 +177,32 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
}
}
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
lifecycleScope.launch{
downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) {
withContext(Dispatchers.Main){
kotlin.runCatching {
UiUtil.handleDownloadsResponse(
requireActivity(),
requireActivity().lifecycleScope,
requireActivity().supportFragmentManager,
res,
downloadViewModel,
historyViewModel)
}
}
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
errorMessage = null,
actions = null
)
}
}
}
initMenu()
initChips()
}

View file

@ -35,6 +35,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar

View file

@ -437,47 +437,51 @@ class InfoUtil(private val context: Context) {
}
private fun getFormatsFromYTDL(url: String) : List<Format> {
val request = YoutubeDLRequest(url)
request.addOption("--print", "%(formats)s")
request.addOption("--print", "%(duration)s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
try {
val request = YoutubeDLRequest(url)
request.addOption("--print", "%(formats)s")
request.addOption("--print", "%(duration)s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--compat-options", "manifest-filesize-approx")
request.addOption("--socket-timeout", "5")
if (sharedPreferences.getBoolean("use_cookies", false)){
FileUtil.getCookieFile(context){
request.addOption("--cookies", it)
if (sharedPreferences.getBoolean("use_cookies", false)){
FileUtil.getCookieFile(context){
request.addOption("--cookies", it)
}
val useHeader = sharedPreferences.getBoolean("use_header", false)
val header = sharedPreferences.getString("useragent_header", "")
if (useHeader && !header.isNullOrBlank()){
request.addOption("--add-header","User-Agent:${header}")
}
}
val useHeader = sharedPreferences.getBoolean("use_header", false)
val header = sharedPreferences.getString("useragent_header", "")
if (useHeader && !header.isNullOrBlank()){
request.addOption("--add-header","User-Agent:${header}")
val proxy = sharedPreferences.getString("proxy", "")
if (proxy!!.isNotBlank()) {
request.addOption("--proxy", proxy)
}
val res = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
res.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(res.out)
}
val json = results[0]
val jsonArray = JSONArray(json)
return parseYTDLFormats(jsonArray)
}catch (e: Exception){
return listOf()
}
val proxy = sharedPreferences.getString("proxy", "")
if (proxy!!.isNotBlank()) {
request.addOption("--proxy", proxy)
}
val res = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
res.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(res.out)
}
val json = results[0]
val jsonArray = JSONArray(json)
return parseYTDLFormats(jsonArray)
}
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
val urlsFile = File(context.cacheDir, "urls.txt")
urlsFile.delete()
urlsFile.createNewFile()
@ -575,6 +579,7 @@ class InfoUtil(private val context: Context) {
request.addOption("-j")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--compat-options", "manifest-filesize-approx")
request.addOption("--socket-timeout", "5")
if (sharedPreferences.getBoolean("use_cookies", false)){
@ -1016,7 +1021,7 @@ class InfoUtil(private val context: Context) {
val aCodecPref = "ba[acodec~='^($preferredAudioCodec)']"
if(downloadItem.type != DownloadViewModel.Type.command){
request.addOption("--trim-filenames", downDir.absolutePath.length + 120)
request.addOption("--trim-filenames", downDir.absolutePath.length + 117)
if (downloadItem.SaveThumb) {
request.addOption("--write-thumbnail")
@ -1161,7 +1166,8 @@ class InfoUtil(private val context: Context) {
if (downloadItem.audioPreferences.embedThumb) {
request.addOption("--embed-thumbnail")
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
if (cropThumb){
try {
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
@ -1222,11 +1228,14 @@ class InfoUtil(private val context: Context) {
val f = StringBuilder()
if(!usingGenericFormat){
if (preferredAudioCodec.isNotBlank()){
f.append("$videof+$aCodecPref/")
if (audiof.isBlank()){
f.append("$videof/bv/best")
}else{
f.append("$videof+$audiof/")
if (preferredAudioCodec.isNotBlank())
f.append("$videof+$aCodecPref/")
f.append("$videof/best")
}
val aa = if (audiof.isNotBlank()) "+$audiof" else ""
f.append("$videof$aa/$videof/best")
if (audiof.contains("+")){
request.addOption("--audio-multistreams")

View file

@ -75,6 +75,14 @@ class NotificationUtil(var context: Context) {
channel.description = description
notificationManager.createNotificationChannel(channel)
//finished or errored downloads
name = resources.getString(R.string.errored_downloads)
description =
resources.getString(R.string.errored_download_notification_channel_description)
channel = NotificationChannel(DOWNLOAD_ERRORED_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
channel.description = description
notificationManager.createNotificationChannel(channel)
//misc
name = resources.getString(R.string.misc)
description = ""
@ -264,7 +272,7 @@ class NotificationUtil(var context: Context) {
logID: Long?,
res: Resources
) {
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
val notificationBuilder = getBuilder(DOWNLOAD_ERRORED_CHANNEL_ID)
val bundle = Bundle()
if (logID != null){
@ -304,7 +312,7 @@ class NotificationUtil(var context: Context) {
notificationBuilder.setContentIntent(errorPendingIntent)
notificationBuilder.addAction(0, res.getString(R.string.logs), errorPendingIntent)
}
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
notificationManager.notify(DOWNLOAD_ERRORED_NOTIFICATION_ID, notificationBuilder.build())
}
@ -540,14 +548,16 @@ class NotificationUtil(var context: Context) {
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
const val DOWNLOAD_FINISHED_CHANNEL_ID = "3"
const val DOWNLOAD_WORKER_CHANNEL_ID = "5"
const val DOWNLOAD_MISC_CHANNEL_ID = "4"
const val DOWNLOAD_ERRORED_CHANNEL_ID = "6"
const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3
const val DOWNLOAD_RESUME_NOTIFICATION_ID = 40000
const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 5
const val FORMAT_UPDATING_NOTIFICATION_ID = 6
const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7
const val DOWNLOAD_MISC_CHANNEL_ID = "4"
const val DOWNLOAD_MISC_NOTIFICATION_ID = 4
const val DOWNLOAD_WORKER_CHANNEL_ID = "5"
const val DOWNLOAD_ERRORED_NOTIFICATION_ID = 6
private const val PROGRESS_MAX = 100
private const val PROGRESS_CURR = 0
}

View file

@ -36,6 +36,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.FileProvider
import androidx.core.view.children
import androidx.core.view.isNotEmpty
import androidx.core.view.isVisible
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.FragmentManager
@ -189,8 +190,13 @@ object UiUtil {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
ok.isEnabled =
!(title.editText!!.text.isEmpty() || content.editText!!.text.isEmpty())
ok.isEnabled = title.editText!!.text.isNotEmpty() &&
content.editText!!.text.isNotEmpty() &&
if (extraCommandsSwitch.isChecked){
((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked))
}else{
true
}
}
})
@ -198,8 +204,14 @@ object UiUtil {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
ok.isEnabled =
(title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty())
ok.isEnabled = title.editText!!.text.isNotEmpty() &&
content.editText!!.text.isNotEmpty() &&
if (extraCommandsSwitch.isChecked){
((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked))
}else{
true
}
if (content.editText!!.text.isNotEmpty()){
content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_all)
}else{
@ -241,11 +253,11 @@ object UiUtil {
}
extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked
ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
}
extraCommandsVideo.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked
ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
}
commandTemplateViewModel.shortcuts.observe(lifeCycle){
@ -1210,6 +1222,7 @@ object UiUtil {
context: Activity,
items: List<DownloadItem>,
embedThumbClicked: (Boolean) -> Unit,
cropThumbClicked: (Boolean) -> Unit,
splitByChaptersClicked: (Boolean) -> Unit,
filenameTemplateSet: (String) -> Unit,
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
@ -1218,9 +1231,18 @@ object UiUtil {
){
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
val cropThumb = view.findViewById<Chip>(R.id.crop_thumb)
embedThumb!!.isChecked = items.all { it.audioPreferences.embedThumb }
cropThumb.isVisible = embedThumb.isChecked
embedThumb.setOnClickListener {
embedThumbClicked(embedThumb.isChecked)
cropThumb.isVisible = embedThumb.isChecked
}
cropThumb!!.isChecked = items.all { it.audioPreferences.cropThumb == true }
cropThumb.setOnClickListener {
cropThumbClicked(cropThumb.isChecked)
}
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)

View file

@ -182,11 +182,15 @@ class UpdateUtil(var context: Context) {
}
updatingYTDL = true
val channelMap = mapOf(
"stable" to YoutubeDL.UpdateChannel._STABLE,
"nightly" to YoutubeDL.UpdateChannel._NIGHTLY,
"master" to YoutubeDL.UpdateChannel._MASTER,
)
val channel = sharedPreferences.getString("ytdlp_source", "nightly")
try {
YoutubeDL.getInstance().updateYoutubeDL(
context, if (sharedPreferences.getBoolean("nightly_ytdl", false) ) YoutubeDL.UpdateChannel._NIGHTLY else YoutubeDL.UpdateChannel._STABLE
).apply {
YoutubeDL.getInstance().updateYoutubeDL(context, channelMap[channel] ?: YoutubeDL.UpdateChannel._NIGHTLY).apply {
updatingYTDL = false
}
}catch (e: Exception){

View file

@ -102,6 +102,7 @@ class DownloadWorker(
notificationUtil.notify(downloadItem.id.toInt(), notification)
CoroutineScope(Dispatchers.IO).launch {
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
downloadItem.status = DownloadRepository.Status.Active.toString()
@ -142,8 +143,6 @@ class DownloadWorker(
dao.update(downloadItem)
}
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
runCatching {
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
@ -212,11 +211,17 @@ class DownloadWorker(
//put download in history
val incognito = sharedPreferences.getBoolean("incognito", false)
if (!incognito) {
val unixtime = System.currentTimeMillis() / 1000
val file = File(finalPaths?.first()!!)
downloadItem.format.filesize = file.length()
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString)
historyDao.insert(historyItem)
if (request.hasOption("--download-archive") && finalPaths == listOf(context.getString(R.string.unfound_file))) {
Looper.prepare().run {
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}
}else{
val unixTime = System.currentTimeMillis() / 1000
val file = File(finalPaths?.first()!!)
downloadItem.format.filesize = file.length()
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixTime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString)
historyDao.insert(historyItem)
}
}
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M20,6h-8l-2,-2H4C2.9,4 2.01,4.9 2.01,6L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8C22,6.9 21.1,6 20,6zM14,16H6v-2h8V16zM18,12H6v-2h12V12z"/>
</vector>

View file

@ -33,6 +33,15 @@
android:checked="false"
android:text="@string/embed_thumb"/>
<com.google.android.material.chip.Chip
android:id="@+id/crop_thumb"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:visibility="gone"
android:text="@string/crop_thumb"/>
<com.google.android.material.chip.Chip
android:id="@+id/split_by_chapters"
style="@style/Widget.Material3.Chip.Filter.Elevated"
@ -41,22 +50,6 @@
android:checked="false"
android:text="@string/split_by_chapters"/>
<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:text="@string/cut"
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>
@ -103,6 +96,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:text="@string/cut"
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>

View file

@ -6,7 +6,7 @@
android:layout_marginBottom="10dp"
android:checkable="true"
android:paddingHorizontal="20dp"
android:paddingVertical="5dp"
android:paddingBottom="5dp"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
@ -18,8 +18,9 @@
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cornerRadius="10dp"
android:autoLink="all"
android:gravity="start"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="10dp"
app:layout_constraintEnd_toStartOf="@+id/already_exists_edit"

View file

@ -133,6 +133,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:visibility="gone"
android:checked="true"
android:text="@string/audio" />
@ -141,6 +142,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:visibility="gone"
android:text="@string/video" />
</LinearLayout>

View file

@ -308,7 +308,6 @@
<string name="export_file">ファイルのエクスポート</string>
<string name="schedule">スケジュール</string>
<string name="user_agent_header">User-Agent ヘッダー</string>
<string name="skip">スキップ</string>
<string name="license">ライセンス</string>
<string name="download_already_exists_summary">再度ダウンロードをしますか\?</string>
<string name="modify_download_card">ダウンロードカードの変更</string>

View file

@ -1081,4 +1081,17 @@
<item>cookies</item>
</array>
<array name="ytdlp_source">
<item>@string/defaultValue</item>
<item>@string/update_ytdl_nightly</item>
<item>@string/update_ytdl_master</item>
</array>
<array name="ytdlp_source_values">
<item>stable</item>
<item>nightly</item>
<item>master</item>
</array>
</resources>

View file

@ -232,7 +232,9 @@
<string name="monochrome">Monochrome</string>
<string name="downloaded">"Downloaded: "</string>
<string name="finished_download_notification_channel_name">Finished Downloads</string>
<string name="finished_download_notification_channel_description">Notification for finished or faulty downloads</string>
<string name="finished_download_notification_channel_description">Notification for finished downloads</string>
<string name="errored_downloads">Errored Downloads</string>
<string name="errored_download_notification_channel_description">Notification for faulty downloads</string>
<string name="quick_download">Quick Download</string>
<string name="quick_download_summary">Don\'t fetch data in the download card</string>
<string name="remove_audio">Remove Audio</string>
@ -319,4 +321,6 @@
<string name="ignore">Ignore</string>
<string name="modify_download_card">Modify Download Card</string>
<string name="export_file">Export File</string>
<string name="ytdl_source">yt-dlp Source</string>
<string name="update_ytdl_master">Master Version of yt-dlp</string>
</resources>

View file

@ -21,13 +21,15 @@
app:key="ytdl-version"
app:title="@string/ytdl_version"/>
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
android:defaultValue="true"
app:icon="@drawable/ic_nightly"
app:key="nightly_ytdl"
android:title="@string/update_ytdl_nightly"
/>
<ListPreference
android:defaultValue="nightly"
android:entries="@array/ytdlp_source"
android:entryValues="@array/ytdlp_source_values"
android:icon="@drawable/baseline_source_24"
app:key="ytdlp_source"
app:useSimpleSummaryProvider="true"
app:title="@string/ytdl_source" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"

View file

@ -14,7 +14,7 @@ buildscript {
commonsIoVer = '2.5'
// supports java 1.6
commonsCompressVer = '1.12'
youtubedlAndroidVer = "23b26d55f8"
youtubedlAndroidVer = "-SNAPSHOT"
workVer = "2.8.1"
composeVer = '1.4.2'
kotlinVer = "1.7.21"