1.7.9.1 prep

This commit is contained in:
zaednasr 2024-08-11 17:28:02 +02:00
parent c40095ac7f
commit 087ba0781a
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
38 changed files with 642 additions and 369 deletions

View file

@ -201,4 +201,5 @@ dependencies {
implementation 'androidx.compose.material3:material3-android:1.2.1'
implementation "io.noties.markwon:core:4.6.2"
implementation("org.greenrobot:eventbus:3.3.1")
//implementation("com.github.TeamNewPipe:NewPipeExtractor:0.24.2")
}

View file

@ -88,5 +88,11 @@
static <1>$Companion Companion;
}
### Rules for NewPipeExtractor
#-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
#-keep class org.mozilla.javascript.** { *; }
#-keep class org.mozilla.classfile.ClassFileWriter
#-dontwarn org.mozilla.javascript.tools.**
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-dontobfuscate

View file

@ -80,24 +80,46 @@
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="youtube.com" />
<data android:scheme="https" android:host="youtube.com" />
<data android:host="youtube.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.youtube.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.youtu.be" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="youtu.be" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="twitter.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.twitter.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -114,7 +136,7 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="soundcloud.com" />
<data android:host="www.x.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -122,162 +144,177 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="soundcloud.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.soundcloud.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="facebook.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.facebook.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="fb.watch" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="instagram.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.instagram.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="tiktok.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.tiktok.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="vm.tiktok.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bilibili.tv" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="youtu.be" />
<data android:host="www.bilibili.tv" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="twitch.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="reddit.com" />
<data android:host="www.twitch.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.reddit.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="reddit.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="m.reddit.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="pinterest.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.pinterest.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="music.youtube.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="m.youtube.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="vk.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.vk.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="rutube.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.rutube.com" />
</intent-filter>
</activity>
<activity

View file

@ -2,6 +2,7 @@ package com.deniscerri.ytdl
import android.Manifest
import android.app.ActionBar.LayoutParams
import android.content.ComponentName
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
@ -32,6 +33,7 @@ import androidx.fragment.app.FragmentContainerView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
@ -290,9 +292,6 @@ class MainActivity : BaseActivity() {
}
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
@ -442,6 +441,8 @@ class MainActivity : BaseActivity() {
e.printStackTrace()
}
}else if (action == Intent.ACTION_VIEW){
navController.popBackStack(navController.graph.startDestinationId, true)
when(intent.getStringExtra("destination")){
"Downloads" -> {
navController.navigate(R.id.historyFragment)

View file

@ -7,6 +7,7 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
@Dao
@ -43,10 +44,10 @@ interface LogDao {
suspend fun updateLog(line: String, id: Long, resetLog: Boolean = false) {
val l = getByID(id) ?: return
var log = l.content ?: ""
if (resetLog) {
log = line.lines().joinToString("\n")
log = if (resetLog) {
line.appendLineToLog()
}else{
log += "\n${line}"
log.appendLineToLog(line)
}
updateLogContent(log, id)
}

View file

@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import com.deniscerri.ytdl.database.models.TerminalItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
@Dao
@ -31,10 +32,10 @@ interface TerminalDao {
suspend fun updateLog(line: String, id: Long, resetLog : Boolean = false){
val t = getTerminalById(id) ?: return
var log = t.log ?: ""
if (resetLog) {
log = line.lines().joinToString("\n")
log = if (resetLog) {
line.lines().joinToString("\n")
}else{
log += "\n${line}"
log.appendLineToLog(line)
}
updateTerminalLog(log, id)
}

View file

@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Parcelable
@ -148,7 +149,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val confTmp = Configuration(application.resources.configuration)
confTmp.setLocale(Locale(sharedPreferences.getString("app_language", "en")!!))
val locale = if (Build.VERSION.SDK_INT < 33) {
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
}else{
Locale.getDefault().language
}.run {
split("-")
}.run {
if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
}
confTmp.setLocale(locale)
val metrics = DisplayMetrics()
resources = Resources(application.assets, metrics, confTmp)
}
@ -245,6 +255,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val preferredAudioFormats = getPreferredAudioFormats(resultItem.formats)
val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
val videoPreferences = VideoPreferences(
embedSubs,
@ -252,6 +263,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
ArrayList(sponsorblock),
saveSubs,
saveAutoSubs,
subsLanguages,
audioFormatIDs = preferredAudioFormats,
recodeVideo = recodeVideo
)
@ -354,8 +366,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val format = getFormat(it.allFormats, type)
it.format = format
var updatedDownloadPath: String = ""
var container: String = ""
var updatedDownloadPath = ""
var container = ""
when(type){
Type.audio -> {
@ -363,11 +375,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
container = sharedPreferences.getString("audio_format", "")!!
}
Type.video -> {
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
container = sharedPreferences.getString("audio_format", "")!!
updatedDownloadPath = sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())!!
container = sharedPreferences.getString("video_format", "")!!
}
Type.command -> {
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
updatedDownloadPath = sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())!!
container = ""
}
else -> {
@ -391,6 +403,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
val customFileNameTemplate = when(historyItem.type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
@ -420,7 +433,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, recodeVideo = recodeVideo)
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, subsLanguages, recodeVideo = recodeVideo)
var path = defaultPath
historyItem.downloadPath.first().apply {
File(this).parent?.apply {
@ -930,10 +943,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}else{
val queued = repository.updateAll(queuedItems)
if (!sharedPreferences.getBoolean("paused_downloads", false)) {
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
if (sharedPreferences.getBoolean("paused_downloads", true)) {
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
resetActiveToQueued()
}
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
if(!useScheduler){
CoroutineScope(Dispatchers.IO).launch {
queued.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {

View file

@ -22,11 +22,16 @@ import com.deniscerri.ytdl.database.repository.SearchHistoryRepository
import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDLException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.util.concurrent.CancellationException
@ -40,18 +45,13 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
private val dao: ResultDao
data class ResultsUiState(
var processing: Boolean,
var errorMessage: Pair<Int, String>?,
var actions: MutableList<Pair<Int, ResultAction>>?
var errorMessage: String?,
var canContinueOnError: Boolean = false
)
enum class ResultAction {
COPY_LOG
}
val uiState: MutableStateFlow<ResultsUiState> = MutableStateFlow(ResultsUiState(
processing = false,
errorMessage = null,
actions = null
))
@ -63,7 +63,8 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
var updateFormatsResultData: MutableStateFlow<MutableList<Format>?> = MutableStateFlow(null)
private var updateFormatsResultDataJob: Job? = null
var parsingQueries: Job? = null
private var parsingQueries: Job? = null
private var parsingQueriesJobList : MutableList<Job> = mutableListOf()
private val sharedPreferences: SharedPreferences
@ -101,6 +102,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
fun cancelParsingQueries(){
parsingQueries?.cancel(CancellationException())
parsingQueriesJobList.forEach { it.cancel(CancellationException()) }
uiState.update {it.copy(processing = false)}
}
@ -110,23 +112,29 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
}
val resetResults = inputQueries.size == 1
uiState.update {it.copy(processing = true, errorMessage = null, actions = null)}
uiState.update {it.copy(processing = true, errorMessage = null)}
val res = mutableListOf<ResultItem?>()
inputQueries.forEach { inputQuery ->
try {
res.addAll(repository.getResultsFromSource(inputQuery, resetResults))
} catch (e: Exception) {
if (updateResultDataJob?.isCancelled == false || e is YoutubeDLException){
uiState.update {it.copy(
processing = false,
errorMessage = Pair(R.string.no_results, e.message.toString()),
actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
)}
Log.e(tag, e.toString())
}
}
val requestSemaphore = Semaphore(10)
inputQueries.forEach { inputQuery ->
parsingQueriesJobList.add(viewModelScope.launch(Dispatchers.IO){
requestSemaphore.withPermit {
try {
res.addAll(repository.getResultsFromSource(inputQuery, resetResults))
} catch (e: Exception) {
if (updateResultDataJob?.isCancelled == false || e is YoutubeDLException){
uiState.update {it.copy(
processing = false,
errorMessage = e.message.toString(),
)}
Log.e(tag, e.toString())
}
}
}
})
}
parsingQueriesJobList.joinAll()
if (!isForegrounded() && inputQueries.size > 1){
notificationUtil.showQueriesFinished()
}
@ -279,17 +287,12 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
uiState.update {
it.copy(
processing = false,
errorMessage = Pair(R.string.no_results, e.message.toString()),
actions = mutableListOf(
Pair(
R.string.copy_log,
ResultAction.COPY_LOG
)
)
errorMessage = e.message.toString(),
)
}
Log.e(tag, e.toString())
}
updatingFormats.emit(false)
}
}
updateFormatsResultDataJob?.start()

View file

@ -114,6 +114,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
private var appBarLayout: AppBarLayout? = null
private var materialToolbar: MaterialToolbar? = null
private var loadingItems: Boolean = false
private var queryList = mutableListOf<String>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@ -255,8 +256,28 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
launch{
resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed ={}) }
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
val isSingleQueryAndURL = queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()
kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, continueAnyway = isSingleQueryAndURL, continued = {
lifecycleScope.launch {
if (sharedPreferences!!.getBoolean("download_card", true)) {
withContext(Dispatchers.Main){
showSingleDownloadSheet(
resultItem = downloadViewModel.createEmptyResultItem(queryList.first()),
type = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!),
disableUpdateData = true
)
}
} else {
val downloadItem = downloadViewModel.createDownloadItemFromResult(
result = downloadViewModel.createEmptyResultItem(queryList.first()),
givenType = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
)
downloadViewModel.queueDownloads(listOf(downloadItem))
}
}
}, closed = {}) }
resultViewModel.uiState.update {it.copy(errorMessage = null) }
}
loadingItems = res.processing
@ -270,9 +291,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
this@HomeFragment.downloadAllFab!!.visibility = VISIBLE
downloadAllFab!!.visibility = VISIBLE
}else{
this@HomeFragment.downloadAllFab!!.visibility = GONE
downloadAllFab!!.visibility = GONE
}
}
}
@ -602,7 +623,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
private fun initSearch(searchView: SearchView){
val queryList = mutableListOf<String>()
queryList = mutableListOf()
if (queriesChipGroup!!.childCount > 0){
queriesChipGroup!!.children.forEach {
val query = (it as Chip).text.toString().trim {it2 -> it2 <= ' '}
@ -689,7 +710,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
@SuppressLint("RestrictedApi")
private fun showSingleDownloadSheet(
resultItem: ResultItem,
type: DownloadViewModel.Type
type: DownloadViewModel.Type,
disableUpdateData : Boolean = false
){
if(findNavController().currentBackStack.value.firstOrNull {it.destination.id == R.id.downloadBottomSheetDialog} == null &&
findNavController().currentDestination?.id == R.id.homeFragment
@ -698,6 +720,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
val bundle = Bundle()
bundle.putParcelable("result", resultItem)
bundle.putSerializable("type", downloadViewModel.getDownloadType(type, resultItem.url))
if (disableUpdateData) {
bundle.putBoolean("disableUpdateData", true)
}
findNavController().navigate(R.id.downloadBottomSheetDialog, bundle)
}
}

View file

@ -76,6 +76,10 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
progressBar.progress = 0
progressBar.isIndeterminate = true
//OUTPUT ------------------------------------------------------------
val output = card.findViewById<TextView>(R.id.output)
output.tag = "${item.id}##output"
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
@ -131,8 +135,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
val pause = popup.menu[0]
val resume = popup.menu[1]
// val pause = popup.menu[0]
// val resume = popup.menu[1]
//
// if (paused){
// pause.isVisible = false

View file

@ -86,6 +86,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var result: ResultItem
private lateinit var type: Type
private var disableUpdateData : Boolean = false
private var currentDownloadItem: DownloadItem? = null
private var incognito: Boolean = false
@ -108,6 +109,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
dwl = arguments?.getParcelable<DownloadItem>("downloadItem")
}
type = arguments?.getSerializable("type") as Type
disableUpdateData = arguments?.getBoolean("disableUpdateData") == true
if (res == null){
dismiss()
@ -429,13 +431,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
//if auto-update after the card is open is off
if (result.title.isEmpty() && currentDownloadItem == null) {
if(sharedPreferences.getBoolean("quick_download", false)) {
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
updateItem.setOnClickListener {
(updateItem.parent as LinearLayout).visibility = View.GONE
initUpdateData()
}
if (result.title.isEmpty() && currentDownloadItem == null && sharedPreferences.getBoolean("quick_download", false)) {
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
updateItem.setOnClickListener {
(updateItem.parent as LinearLayout).visibility = View.GONE
initUpdateData()
}
}else{
(updateItem.parent as LinearLayout).visibility = View.GONE
@ -463,24 +463,28 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
//update in the background if there is no data
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
initUpdateData()
}else {
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
initUpdateFormats(result)
if (!disableUpdateData) {
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
initUpdateData()
}else {
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
initUpdateFormats(result)
}
}
}
lifecycleScope.launch {
resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed = {
kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, true, continued = {}, closed = {
dismiss()
}) }
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
resultViewModel.uiState.update {it.copy(errorMessage = null) }
}
}
}

View file

@ -774,6 +774,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val fileSizes = mutableListOf<Long>()
items.forEach {
if (it.type == DownloadViewModel.Type.video){
println(it.format.filesize)
if (it.format.filesize <= 5L) {
fileSizes.add(0)
}else{
@ -787,6 +788,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
}
fileSizes.add(it.format.filesize + audioFormatSize)
}
}else if (it.type == DownloadViewModel.Type.audio){
fileSizes.add(it.format.filesize)
}
}

View file

@ -114,7 +114,7 @@ class FormatSelectionBottomSheetDialog(
return
}
items = _items.distinctBy { it!!.url }.toMutableList()
items = _items.toMutableList()
if (items.size == 1) {
formats = items.first()!!.allFormats
}else{
@ -192,8 +192,10 @@ class FormatSelectionBottomSheetDialog(
refreshBtn.setOnClickListener {
lifecycleScope.launch {
val itemsThatHaveFormats = items.filter { it!!.allFormats.isNotEmpty() }
val itemsWithMissingFormats = items.filter { it!!.allFormats.isEmpty() }.ifEmpty { items }
val distinctItems = items.distinctBy { it!!.url }
val itemsThatHaveFormats = distinctItems.filter { it!!.allFormats.isNotEmpty() }
val itemsWithMissingFormats = distinctItems.filter { it!!.allFormats.isEmpty() }.ifEmpty { distinctItems }
if (itemsWithMissingFormats.size > 10){
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
@ -238,11 +240,7 @@ class FormatSelectionBottomSheetDialog(
}
}.onFailure { err ->
withContext(Dispatchers.Main){
UiUtil.handleResultResponse(requireActivity(), ResultViewModel.ResultsUiState(
false,
Pair(R.string.no_results, err.message.toString()),
mutableListOf(Pair(R.string.copy_log, ResultViewModel.ResultAction.COPY_LOG))
), closed = {})
UiUtil.handleNoResults(requireActivity(), err.message.toString(), false, continued = {}, closed = {})
}
}
@ -270,9 +268,9 @@ class FormatSelectionBottomSheetDialog(
}
}else{
multipleFormatsListener.onFormatUpdated(it.url, it.formats)
items.firstOrNull { item -> item!!.url == it.url }?.apply {
allFormats.clear()
allFormats.addAll(it.formats)
items.filter { item -> item!!.url == it.url }.forEach { d ->
d?.allFormats?.clear()
d?.allFormats?.addAll(it.formats)
}
progressInt++
lifecycleScope.launch(Dispatchers.Main) {
@ -290,7 +288,7 @@ class FormatSelectionBottomSheetDialog(
val flatFormatCollection = formatCollection.flatten()
val commonFormats =
flatFormatCollection.groupingBy { it.format_id }.eachCount()
.filter { it.value == items.size }
.filter { it.value == distinctItems.size }
.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }
.map { it.value }
formats.addAll(commonFormats)

View file

@ -53,6 +53,7 @@ import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.VideoPlayerUtil
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -72,6 +73,9 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -86,8 +90,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
private lateinit var queuedAdapter: GenericDownloadAdapter
private var activeCount: Int = 0
private lateinit var downloadManager: DownloadManager
private lateinit var sharedPreferences: SharedPreferences
@ -97,6 +100,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
infoUtil = InfoUtil(requireActivity())
notificationUtil = NotificationUtil(requireActivity())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
@ -237,12 +241,6 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
}
}
lifecycleScope.launch {
downloadViewModel.activeDownloadsCount.collectLatest {
activeCount = it
}
}
bottomSheetLink.text = item.url
bottomSheetLink.setOnClickListener{
@ -482,21 +480,42 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
override fun onCancelClick(itemID: Long) {
lifecycleScope.launch {
if (activeCount == 1){
val queue = withContext(Dispatchers.IO){
val list = downloadViewModel.getQueued().toMutableList()
list.map { it.status = DownloadRepository.Status.Queued.toString() }
list
}
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
notificationUtil.cancelDownloadNotification(itemID.toInt())
runBlocking {
downloadViewModel.queueDownloads(queue)
val item = withContext(Dispatchers.IO){
downloadViewModel.getItemByID(itemID)
}
item.status = DownloadRepository.Status.Cancelled.toString()
withContext(Dispatchers.IO){
downloadViewModel.updateDownload(item)
}
val activeCount = withContext(Dispatchers.IO){
downloadViewModel.getActiveDownloadsCount()
}
if (activeCount == 0){
val queuedCount = withContext(Dispatchers.IO){
downloadViewModel.getQueuedDownloadsCount()
}
if (queuedCount == 0) {
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
}
}
}
if (activeCount == 1){
val queue = withContext(Dispatchers.IO){
downloadViewModel.getQueued()
}
cancelActiveDownload(itemID)
if (!sharedPreferences.getBoolean("paused_downloads", false)){
runBlocking {
downloadViewModel.queueDownloads(queue)
}
}
}
}
}
override fun onCardClick() {
this.dismiss()
@ -505,22 +524,29 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
)
}
private fun cancelActiveDownload(itemID: Long){
lifecycleScope.launch {
val id = itemID.toInt()
YoutubeDL.getInstance().destroyProcessById(id.toString())
notificationUtil.cancelDownloadNotification(id)
//dont remove
@Subscribe(threadMode = ThreadMode.MAIN)
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
withContext(Dispatchers.IO){
downloadViewModel.getItemByID(itemID)
}.let {
it.status = DownloadRepository.Status.Cancelled.toString()
lifecycleScope.launch(Dispatchers.IO){
downloadViewModel.updateDownload(it)
}
}
requireActivity().runOnUiThread {
try {
progressBar?.setProgressCompat(event.progress, true)
outputText?.text = event.output
}catch (ignored: Exception) {}
}
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
}

View file

@ -48,6 +48,9 @@ class DownloadLogFragment : Fragment() {
private lateinit var logViewModel: LogViewModel
private lateinit var sharedPreferences: SharedPreferences
private var autoScroll : Boolean = true
private var scrollDownBtn : MenuItem? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -113,6 +116,7 @@ class DownloadLogFragment : Fragment() {
contentScrollView.enableFastScroll()
scrollDownBtn = bottomAppBar?.menu?.children?.first { it.itemId == R.id.scroll_down }
val slider = view.findViewById<Slider>(R.id.textsize_seekbar)
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
@ -124,16 +128,17 @@ class DownloadLogFragment : Fragment() {
scrollView.removeAllViews()
parent.removeView(scrollView)
parent.addView(content, 0)
contentScrollView.setPadding(0,0,0,
(requireContext().resources.displayMetrics.density * 150).toInt()
)
// contentScrollView.setPadding(0,0,0,
// (requireContext().resources.displayMetrics.density * 150).toInt()
// )
updateAutoScrollState()
}else{
val parent = content.parent as ViewGroup
parent.removeView(content)
scrollView = HorizontalScrollView(requireContext())
scrollView.setPadding(0,0,0,
(requireContext().resources.displayMetrics.density * 150).toInt()
)
// scrollView.setPadding(0,0,0,
// (requireContext().resources.displayMetrics.density * 150).toInt()
// )
contentScrollView.setPadding(0,0,0,0)
scrollView.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
@ -142,11 +147,13 @@ class DownloadLogFragment : Fragment() {
scrollView.addView(content)
scrollView.id = R.id.horizontalscroll_output
parent.addView(scrollView, 0)
updateAutoScrollState()
}
}
R.id.scroll_down -> {
m.isVisible = false
autoScroll = true
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
@ -171,9 +178,12 @@ class DownloadLogFragment : Fragment() {
}
contentScrollView.setOnScrollChangeListener { view, sx, sy, osx, osy ->
if (sy < osy){
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
}
updateAutoScrollState()
}
contentScrollView.setOnTouchListener { view, motionEvent ->
autoScroll = false
false
}
@ -185,9 +195,11 @@ class DownloadLogFragment : Fragment() {
content.setText(logItem.content, TextView.BufferType.SPANNABLE)
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
}
if (!bottomAppBar.menu.children.first { it.itemId == R.id.scroll_down }.isVisible){
content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
if (autoScroll){
//content.scrollTo(0, content.height)
content.post {
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
}
}
}
@ -195,6 +207,11 @@ class DownloadLogFragment : Fragment() {
}
}
private fun updateAutoScrollState() {
val canVerticallyScroll = contentScrollView.canScrollVertically(1)
scrollDownBtn?.isVisible = canVerticallyScroll
}
companion object {
private const val TAG = "DownloadLogActivity"
}

View file

@ -81,8 +81,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
editor.putString("command_path", FileUtil.getDefaultCommandPath())
}
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
VERSION.SDK_INT < 30) {
if (FileUtil.hasAllFilesAccess()) {
accessAllFiles!!.isVisible = false
cacheDownloads!!.isEnabled = true
}else{
@ -90,8 +89,6 @@ class FolderSettingsFragment : BaseSettingsFragment() {
cacheDownloads!!.isEnabled = false
}
editor.apply()
musicPath!!.summary = FileUtil.formatPath(preferences.getString("music_path", "")!!)
musicPath!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {

View file

@ -261,20 +261,27 @@ object Extensions {
}
}
// fun String.appendLineToLog(line: String): String {
// val lines = this.split("\n")
// if (!lines.takeLast(3).contains(line)){
// //clean dublicate progress + add newline
// var newLine = line
// if (newLine.contains("[download")) {
// newLine = "[download]" + line.split("[download]").last()
// }
//
// return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
// }
//
// return this
// }
fun String.appendLineToLog(line: String = ""): String {
val lines = this.lines().toMutableList()
val finishingProgressLinesRegex = Pattern.compile("\\[download]\\h+(100%|[a-zA-Z])")
if (line.isNotBlank()) {
var newLine = ""
val newLines = line.lines()
if (!lines.takeLast(3).any { newLines.contains(it) }) {
lines.addAll(newLines.dropLast(1))
newLine = "\n${newLines.last()}"
}
return lines.dropLastWhile {
it.contains("[download") && !finishingProgressLinesRegex.matcher(it).find()
}.joinToString("\n") + newLine
}
return lines.filterNot {
it.contains("[download") && !finishingProgressLinesRegex.matcher(it).find()
}.joinToString("\n")
}
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
if(!hideThumb){

View file

@ -400,4 +400,9 @@ object FileUtil {
}
}
fun hasAllFilesAccess() : Boolean {
if (Build.VERSION.SDK_INT < 30) return true
return Environment.isExternalStorageManager()
}
}

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.os.Handler
import android.os.Looper
import android.text.Html
@ -43,7 +42,6 @@ import java.lang.reflect.Type
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import java.util.regex.Pattern
class InfoUtil(private val context: Context) {
@ -168,6 +166,13 @@ class InfoUtil(private val context: Context) {
fun getYoutubeVideo(url: String): List<ResultItem>? {
val theURL = url.replace("\\?list.*".toRegex(), "")
try{
//TODO NEWPIPE TEST
// val extractor = ServiceList.YouTube.getStreamExtractor(url)
// extractor.fetchPage()
// val extractor2 = ServiceList.YouTube.getPlaylistExtractor(url)
// extractor2.fetchPage()
// var info = extractor2.getPage(extractor2.initialPage.nextPage)
val id = getIDFromYoutubeURL(theURL)
val res = genericRequest("$pipedURL/streams/$id")
if (res.length() == 0) {
@ -678,7 +683,7 @@ class InfoUtil(private val context: Context) {
}
val lang = sharedPreferences.getString("app_language", "en")
if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
var extractorArgs = "player_client=default,mediaconnect,android"
var extractorArgs = "player_client=default,mediaconnect"
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang"
}
@ -1034,6 +1039,12 @@ class InfoUtil(private val context: Context) {
}
}
private fun MutableList<String>.addOption(vararg options: String) {
options.forEach {
this.add(it)
}
}
@SuppressLint("RestrictedApi")
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest {
@ -1052,14 +1063,16 @@ class InfoUtil(private val context: Context) {
}
}
val metadataCommands = mutableListOf<String>()
if (downloadItem.playlistIndex != null && useItemURL) {
request.addOption("--parse-metadata", " ${downloadItem.playlistIndex}: %(playlist_index)s")
metadataCommands.addOption("--parse-metadata", " ${downloadItem.playlistIndex}: %(playlist_index)s")
}
val type = downloadItem.type
val downDir : File
val canWrite = File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite() || sharedPreferences.getBoolean("access_all_files", false)
val canWrite = File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
if (!sharedPreferences.getBoolean("cache_downloads", true) && canWrite){
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
request.addOption("--no-quiet")
@ -1162,18 +1175,14 @@ class InfoUtil(private val context: Context) {
}
if(downloadItem.title.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(180)))
metadataCommands.addOption("--replace-in-metadata", "title", ".+", downloadItem.title.take(180))
metadataCommands.addOption("--parse-metadata", "%(title)s:%(meta_title)s")
}
if (downloadItem.author.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30)))
}
request.addOption("--parse-metadata", "uploader:^(?P<uploader>.*?)(?:(?= - Topic)|$)")
if (embedMetadata){
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
metadataCommands.addOption("--replace-in-metadata", "uploader", ".+", downloadItem.author.take(30))
metadataCommands.addOption("--parse-metadata", "%(uploader)s:%(artist)s")
}
if (downloadItem.downloadSections.isNotBlank()){
@ -1196,7 +1205,7 @@ class InfoUtil(private val context: Context) {
}
if (sharedPreferences.getBoolean("use_audio_quality", false)){
request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0))
request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0).toString())
}
if (sharedPreferences.getBoolean("write_description", false)){
@ -1204,7 +1213,7 @@ class InfoUtil(private val context: Context) {
}
if (downloadItem.url.isYoutubeURL()) {
var extractorArgs = "player_client=default,mediaconnect,android"
var extractorArgs = "player_client=default,mediaconnect"
val lang = sharedPreferences.getString("app_language", "en")
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang"
@ -1261,7 +1270,7 @@ class InfoUtil(private val context: Context) {
}
request.addOption("-x")
val formatSorting = StringBuilder("hasaud")
val formatSorting = StringBuilder("hasaud,size")
if (aCodecPref.isNotBlank()){
formatSorting.append(",acodec:$aCodecPref")
@ -1285,31 +1294,46 @@ class InfoUtil(private val context: Context) {
request.addOption("-P", downDir.absolutePath)
request.addOption("-S", formatSorting.toString())
metadataCommands.addOption("--parse-metadata", """%(uploader,artist,channel,creator|null)s:^(?P<uploader>.*?)(?:(?= - Topic)|$)""")
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters")
request.addOption("-o", "chapter:%(section_title)s.%(ext)s")
}else{
if (embedMetadata){
request.addOption("--embed-metadata")
request.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)")
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist|)s:%(album_artist)s")
metadataCommands.addOption("--embed-metadata")
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:(?P<meta_date>\\d+)")
val emptyAuthor = downloadItem.author.isEmpty()
val usePlaylistMetadata = sharedPreferences.getBoolean("playlist_as_album", true)
if (downloadItem.playlistTitle.isNotEmpty()) {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)")
} else {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
if (emptyAuthor) {
if (usePlaylistMetadata) {
metadataCommands.addOption("--parse-metadata", "%(playlist_uploader,artist,uploader|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
}else{
metadataCommands.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
}
}else{
if (usePlaylistMetadata) {
metadataCommands.addOption("--parse-metadata", "%(playlist_uploader,artist|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
}else{
metadataCommands.addOption("--parse-metadata", "%(artist|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
}
}
metadataCommands.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
metadataCommands.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\\d+)")
metadataCommands.addOption("--parse-metadata", "%(album_artist,first_artist|)s:%(album_artist)s")
metadataCommands.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)")
}
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
if (downloadItem.audioPreferences.embedThumb){
request.addOption("--embed-thumbnail")
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
metadataCommands.addOption("--embed-thumbnail")
if (!request.hasOption("--convert-thumbnails")) metadataCommands.addOption("--convert-thumbnails", thumbnailFormat!!)
val thumbnailConfig = StringBuilder("")
val cropConfig = """-vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"""""
@ -1326,7 +1350,7 @@ class InfoUtil(private val context: Context) {
runCatching {
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
config.writeText(thumbnailConfig.toString())
request.addOption("--config", config.absolutePath)
metadataCommands.addOption("--config", config.absolutePath)
}
}
@ -1339,6 +1363,8 @@ class InfoUtil(private val context: Context) {
}
DownloadViewModel.Type.video -> {
metadataCommands.addOption("--parse-metadata", """%(uploader,channel,creator,artist|null)s:^(?P<uploader>.*?)(?:(?= - Topic)|$)""")
val supportedContainers = context.resources.getStringArray(R.array.video_containers)
if (downloadItem.videoPreferences.addChapters) {
@ -1359,7 +1385,8 @@ class InfoUtil(private val context: Context) {
){
cont = outputContainer
if (downloadItem.videoPreferences.recodeVideo) {
val cantRecode = listOf("avi")
if (downloadItem.videoPreferences.recodeVideo && !cantRecode.contains(cont)) {
request.addOption("--recode-video", outputContainer.lowercase())
}else{
request.addOption("--merge-output-format", outputContainer.lowercase())
@ -1368,7 +1395,7 @@ class InfoUtil(private val context: Context) {
if (!listOf("webm", "avi", "flv").contains(outputContainer.lowercase())) {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) {
request.addOption("--embed-thumbnail")
metadataCommands.addOption("--embed-thumbnail")
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
}
}
@ -1382,7 +1409,6 @@ class InfoUtil(private val context: Context) {
var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
val format = downloadItem.allFormats.find { it.format_id == f }
format?.run {
println(format_id)
if (this.format_id.matches(".*-[0-9]+".toRegex())) {
if (!this.lang.isNullOrBlank() && this.lang != "None") {
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]"
@ -1591,7 +1617,9 @@ class InfoUtil(private val context: Context) {
)
}
if (metadataCommands.isNotEmpty()){
request.addCommands(metadataCommands)
}
return request
}

View file

@ -35,6 +35,7 @@ object NavbarUtil {
val pref = settings.getString("start_destination", "")!!
val items = getDefaultNavBarItems(context)
val navBarHasPref = getNavBarPrefs().contains(items.indexOfFirst { it.itemId == navItems[pref] }.toString())
println("START FRAGMENT: $pref")
return if (pref == "") {
R.id.homeFragment
}else {

View file

@ -20,6 +20,7 @@ import androidx.core.content.FileProvider
import androidx.core.os.bundleOf
import androidx.documentfile.provider.DocumentFile
import androidx.navigation.NavDeepLinkBuilder
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver
@ -148,11 +149,9 @@ class NotificationUtil(var context: Context) {
fun createDownloadServiceNotification(
pendingIntent: PendingIntent?,
title: String?,
itemID: Int,
): Notification {
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
return notificationBuilder
.setContentTitle(title)
.setOngoing(true)

View file

@ -103,6 +103,7 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.Queue
import java.util.function.Predicate
object UiUtil {
@ -862,14 +863,16 @@ object UiUtil {
true
}
if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE
fun String?.doesntExist() : Boolean { return this.isNullOrBlank() || this == "None"}
if (format.format_id.doesntExist()) formatIdParent?.visibility = View.GONE
else {
formatIdParent?.findViewById<TextView>(R.id.format_id_value)?.text = format.format_id
formatIdParent?.setOnClickListener(clicker)
formatIdParent?.setOnLongClickListener(longClicker)
}
if (format.url.isNullOrBlank()) formatURLParent?.visibility = View.GONE
if (format.url.doesntExist()) formatURLParent?.visibility = View.GONE
else {
formatURLParent?.findViewById<TextView>(R.id.format_url_value)?.text = format.url
formatURLParent?.setOnClickListener(clicker)
@ -877,7 +880,7 @@ object UiUtil {
}
if (format.container.isBlank()) containerParent?.visibility = View.GONE
if (format.container.doesntExist()) containerParent?.visibility = View.GONE
else {
containerParent?.findViewById<TextView>(R.id.container_value)?.text = format.container
containerParent?.setOnClickListener(clicker)
@ -893,7 +896,7 @@ object UiUtil {
format.acodec.uppercase()
}
if (codecField.isBlank()) codecParent?.visibility = View.GONE
if (codecField.doesntExist()) codecParent?.visibility = View.GONE
else {
codecParent?.findViewById<TextView>(R.id.codec_value)?.text = codecField
codecParent?.setOnClickListener(clicker)
@ -907,28 +910,28 @@ object UiUtil {
filesizeParent?.setOnLongClickListener(longClicker)
}
if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE
if (format.format_note.doesntExist()) formatnoteParent?.visibility = View.GONE
else {
formatnoteParent?.findViewById<TextView>(R.id.format_note_value)?.text = format.format_note
formatnoteParent?.setOnClickListener(clicker)
formatnoteParent?.setOnLongClickListener(longClicker)
}
if (format.fps.isNullOrBlank() || format.fps == "0") fpsParent?.visibility = View.GONE
if (format.fps.doesntExist() || format.fps == "0") fpsParent?.visibility = View.GONE
else {
fpsParent?.findViewById<TextView>(R.id.fps_value)?.text = format.fps
fpsParent?.setOnClickListener(clicker)
fpsParent?.setOnLongClickListener(longClicker)
}
if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE
if (format.asr.doesntExist()) asrParent?.visibility = View.GONE
else {
asrParent?.findViewById<TextView>(R.id.asr_value)?.text = format.asr
asrParent?.setOnClickListener(clicker)
asrParent?.setOnLongClickListener(longClicker)
}
if (format.tbr.isNullOrBlank()) bitrateParent?.visibility = View.GONE
if (format.tbr.doesntExist()) bitrateParent?.visibility = View.GONE
else{
bitrateParent?.findViewById<TextView>(R.id.bitrate_value)?.text = format.tbr
bitrateParent?.setOnClickListener(clicker)
@ -990,7 +993,21 @@ object UiUtil {
val chips = mutableListOf<Chip>()
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
tmp.text = Locale(s).displayLanguage
s.split("-", ".*").run {
if (s.endsWith(".*")) {
val name = "${Locale(this[0]).displayName} (${context.getString(R.string.all)})"
tmp.text = name
}else{
if (this.size == 1) {
Locale(this[0])
} else {
Locale(this[0], this[1])
}.apply {
tmp.text = this.displayName
}
}
}
tmp.tag = s
tmp.id = index
@ -1564,24 +1581,20 @@ object UiUtil {
}
fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState, closed: () -> Unit){
val title = context.getString(it.errorMessage!!.first)
val message = it.errorMessage!!.second
fun handleNoResults(context: Activity, message: String, continueAnyway: Boolean = false, continued: () -> Unit, closed: () -> Unit) {
val errDialog = MaterialAlertDialogBuilder(context)
.setTitle(title)
.setTitle(R.string.no_results)
.setMessage(message)
for (a in it.actions!!){
when(a.second){
ResultViewModel.ResultAction.COPY_LOG -> {
errDialog.setPositiveButton(a.first) { d:DialogInterface?, _:Int ->
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(message)
d?.dismiss()
}
}
errDialog.setPositiveButton(R.string.copy_log) { d:DialogInterface?, _:Int ->
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(message)
d?.dismiss()
}
if (continueAnyway) {
errDialog.setNeutralButton(R.string.continue_anyway) {d: DialogInterface?, _:Int ->
continued()
}
}

View file

@ -1,9 +1,12 @@
package com.deniscerri.ytdl.work
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.DisplayMetrics
@ -20,6 +23,7 @@ import androidx.work.await
import androidx.work.workDataOf
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.models.HistoryItem
@ -73,15 +77,28 @@ class DownloadWorker(
//val createResultItem = inputData.getBoolean("createResultItem", true)
val confTmp = Configuration(context.resources.configuration)
val currLang = sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }.split("-")
confTmp.setLocale(if (currLang.size == 1) Locale(currLang[0]) else Locale(currLang[0], currLang[1]))
val locale = if (Build.VERSION.SDK_INT < 33) {
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
}else{
Locale.getDefault().language
}.run {
split("-")
}.run {
if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
}
confTmp.setLocale(locale)
val metrics = DisplayMetrics()
val resources = Resources(context.assets, metrics, confTmp)
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.downloadQueueMainFragment)
.createPendingIntent()
val openQueueIntent = Intent(context, MainActivity::class.java)
openQueueIntent.setAction(Intent.ACTION_VIEW)
openQueueIntent.putExtra("destination", "Queue")
val openDownloadQueue = PendingIntent.getActivity(
context,
1000000000,
openQueueIntent,
PendingIntent.FLAG_IMMUTABLE
)
val workNotif = notificationUtil.createDefaultWorkerNotification()
val foregroundInfo = ForegroundInfo(1000000000, workNotif)
@ -112,7 +129,7 @@ class DownloadWorker(
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
eligibleDownloads.forEach{downloadItem ->
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title.ifEmpty { downloadItem.url }, downloadItem.id.toInt())
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
notificationUtil.notify(downloadItem.id.toInt(), notification)
CoroutineScope(Dispatchers.IO).launch {
@ -189,7 +206,7 @@ class DownloadWorker(
}
//val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking {
var finalPaths : MutableList<String>?
var finalPaths = mutableListOf<String>()
if (noCache){
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id))
@ -212,9 +229,6 @@ class DownloadWorker(
finalPaths.sortBy { File(it).lastModified() }
finalPaths = finalPaths.distinct().toMutableList()
FileUtil.scanMedia(finalPaths, context)
if (finalPaths.isEmpty()){
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
}
}else{
//move file from internal to set download directory
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
@ -227,11 +241,8 @@ class DownloadWorker(
if (finalPaths.isNotEmpty()){
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
}else{
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
}
}catch (e: Exception){
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
@ -246,51 +257,52 @@ class DownloadWorker(
add("description")
add("txt")
}
finalPaths = finalPaths?.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }?.toMutableList()
finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList()
if (finalPaths.isEmpty()){
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
}
FileUtil.deleteConfigFiles(request)
//put download in history
if (!downloadItem.incognito) {
if (request.hasOption("--download-archive") && finalPaths == listOf(context.getString(R.string.unfound_file))) {
if (request.hasOption("--download-archive") && finalPaths.isEmpty()) {
handler.postDelayed({
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}, 100)
}else{
val unixTime = System.currentTimeMillis() / 1000
finalPaths?.apply {
this.first().apply {
val file = File(this)
var duration = downloadItem.duration
val d = file.getMediaDuration(context)
if (d > 0) duration = d.toStringDuration(Locale.US)
finalPaths.first().apply {
val file = File(this)
var duration = downloadItem.duration
val d = file.getMediaDuration(context)
if (d > 0) duration = d.toStringDuration(Locale.US)
downloadItem.format.filesize = file.length()
downloadItem.format.container = file.extension
downloadItem.duration = duration
}
val historyItem = HistoryItem(0,
downloadItem.url,
downloadItem.title,
downloadItem.author,
downloadItem.duration,
downloadItem.thumb,
downloadItem.type,
unixTime,
this,
downloadItem.website,
downloadItem.format,
downloadItem.id,
commandString)
historyDao.insert(historyItem)
downloadItem.format.filesize = file.length()
downloadItem.format.container = file.extension
downloadItem.duration = duration
}
val historyItem = HistoryItem(0,
downloadItem.url,
downloadItem.title,
downloadItem.author,
downloadItem.duration,
downloadItem.thumb,
downloadItem.type,
unixTime,
finalPaths,
downloadItem.website,
downloadItem.format,
downloadItem.id,
commandString)
historyDao.insert(historyItem)
}
}
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished(
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
)
// if (wasQuickDownloaded && createResultItem){

View file

@ -49,7 +49,7 @@ class TerminalDownloadWorker(
val intent = Intent(context, TerminalActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), itemId)
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65))
val foregroundInfo = ForegroundInfo(itemId, notification)
setForegroundAsync(foregroundInfo)

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?android:colorAccent" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,16.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zM12,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1z"/>
</vector>

View file

@ -8,5 +8,5 @@
android:bottom="1dp"
android:top="1dp" />
<corners android:radius="3dp" />
<corners android:radius="5dp" />
</shape>

View file

@ -34,7 +34,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:paddingVertical="10dp"
android:layout_height="80dp">
android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/downloads_image_view"
@ -49,13 +49,12 @@
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/download_item_data"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@+id/options"
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
app:layout_constraintTop_toTopOf="parent"
@ -66,7 +65,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:maxLines="1"
android:paddingHorizontal="5dp"
android:scrollbars="none"
android:textSize="15sp"
@ -76,11 +75,11 @@
app:layout_constraintTop_toTopOf="parent" />
<HorizontalScrollView
android:id="@+id/horizontalScrollView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
@ -90,27 +89,30 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/download_type"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorPrimaryContainer"
android:backgroundTint="?attr/colorSecondary"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:drawableStartCompat="@drawable/ic_music_formatcard"
app:drawableTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/file_size"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
@ -123,7 +125,6 @@
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
@ -142,10 +143,8 @@
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
@ -161,10 +160,11 @@
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorSecondary"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
@ -172,12 +172,24 @@
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/output"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:maxLines="2"
android:ellipsize="end"
android:layout_margin="5dp"
android:textSize="13sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/horizontalScrollView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -38,7 +38,7 @@
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/incognitoLabel"

View file

@ -120,7 +120,6 @@
android:textStyle="bold"
app:cornerRadius="10dp"
app:drawableStartCompat="@drawable/ic_music_formatcard"
app:drawableTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -173,6 +172,7 @@
<TextView
android:id="@+id/format_id"
android:layout_width="0dp"
android:minWidth="70dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"

View file

@ -1,31 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:id="@+id/appbarlayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:liftOnScroll="false"
android:background="@android:color/transparent"
android:fitsSystemWindows="true">
android:fitsSystemWindows="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/title"
android:elevation="0dp"
android:layout_width="match_parent"
app:navigationIcon="@drawable/ic_back"
android:layout_height="match_parent"
android:elevation="0dp"
app:navigationContentDescription="@string/back"
android:layout_height="match_parent"/>
app:navigationIcon="@drawable/ic_back" />
<com.google.android.material.slider.Slider
android:visibility="gone"
android:id="@+id/textsize_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appbar" />
@ -34,34 +36,45 @@
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/frameLayout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appbarlayout">
<me.zhanghai.android.fastscroll.FastScrollScrollView
android:id="@+id/content_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
>
android:clipToPadding="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="150dp"
android:focusableInTouchMode="true"
android:orientation="vertical">
<TextView
android:id="@+id/content"
android:padding="10dp"
android:fontFamily="monospace"
android:textSize="15sp"
android:gravity="bottom"
android:scrollbars="none"
<HorizontalScrollView
android:layout_width="wrap_content"
android:textIsSelectable="true"
android:layout_height="wrap_content" />
android:id="@+id/horizontalscroll_output"
android:layout_height="wrap_content">
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:gravity="bottom"
android:padding="10dp"
android:scrollbars="none"
android:textIsSelectable="true"
android:textSize="15sp" />
</HorizontalScrollView>
</LinearLayout>
@ -69,27 +82,41 @@
</FrameLayout>
<include layout="@layout/no_results"
android:visibility="gone" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
android:layout_width="match_parent"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
style="@style/Widget.Material3.BottomAppBar"
app:menu="@menu/log_detail_menu"
/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/copy_log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/copy_log"
android:text="@string/copy_log"
app:layout_anchor="@id/bottomAppBar"
app:icon="@drawable/ic_copy"/>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
style="@style/Widget.Material3.BottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_constraintBottom_toBottomOf="@+id/frameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:menu="@menu/log_detail_menu" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/copy_log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/copy_log"
android:text="@string/copy_log"
app:icon="@drawable/ic_copy"
app:layout_anchor="@id/bottomAppBar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<include
layout="@layout/no_results"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/appbarlayout"
app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout3"
app:layout_constraintEnd_toEndOf="@+id/frameLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -67,7 +67,7 @@
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
<TextView

View file

@ -101,9 +101,12 @@
android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
app:elevation="0dp"
android:elevation="0dp"
app:borderWidth="0dp"
android:contentDescription="@string/audio"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
android:layout_marginHorizontal="5dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toTopOf="parent" />
@ -113,8 +116,9 @@
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
app:elevation="0dp"
android:contentDescription="@string/video"
android:layout_marginHorizontal="10dp"
android:layout_marginHorizontal="5dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -3,19 +3,19 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" >
<group>
<item
android:id="@+id/pause"
android:title="@string/pause"
android:icon="@drawable/exomedia_ic_pause_white"
app:showAsAction="ifRoom" />
<!-- <group>-->
<!-- <item-->
<!-- android:id="@+id/pause"-->
<!-- android:title="@string/pause"-->
<!-- android:icon="@drawable/exomedia_ic_pause_white"-->
<!-- app:showAsAction="ifRoom" />-->
<item
android:id="@+id/resume"
android:title="@string/resume"
android:icon="@drawable/exomedia_ic_play_arrow_white"
app:showAsAction="ifRoom" />
</group>
<!-- <item-->
<!-- android:id="@+id/resume"-->
<!-- android:title="@string/resume"-->
<!-- android:icon="@drawable/exomedia_ic_play_arrow_white"-->
<!-- app:showAsAction="ifRoom" />-->
<!-- </group>-->
<item
android:id="@+id/cancel"

View file

@ -12,7 +12,6 @@
<item
android:id="@+id/scroll_down"
android:title="@string/scroll_down"
android:visible="false"
android:icon="@drawable/ic_down"
app:showAsAction="always" />

View file

@ -1029,13 +1029,16 @@
<item>en-IN</item>,
<item>en-GB</item>,
<item>en</item>,
<item>en.*</item>,
<item>es</item>,
<item>es-419</item>,
<item>es-US</item>,
<item>es.*</item>,
<item>eu</item>,
<item>fil</item>,
<item>fr</item>,
<item>fr-CA</item>,
<item>fr.*</item>,
<item>gl</item>,
<item>hr</item>,
<item>zu</item>,
@ -1051,6 +1054,7 @@
<item>pl</item>,
<item>pt-PT</item>,
<item>pt</item>,
<item>pt.*</item>,
<item>ro</item>,
<item>sq</item>,
<item>sk</item>,
@ -1097,6 +1101,7 @@
<item>zh-CN</item>,
<item>zh-TW</item>,
<item>zh-HK</item>,
<item>zh.*</item>,
<item>ja</item>,
<item>ko</item>
</string-array>

View file

@ -418,4 +418,7 @@
<string name="weekly">Weekly</string>
<string name="monthly">Monthly</string>
<string name="cleanup_leftover_downloads">Clean-up leftover downloads (cancelled, errored)</string>
<string name="continue_anyway">Continue Anyway</string>
<string name="playlist_as_album">Use Playlist Name as Album Metadata</string>
<string name="playlist_as_album_summary">If Album Metadata is not present, use playlist name instead</string>
</resources>

View file

@ -32,6 +32,11 @@
<item name="cornerSize">5dp</item>
</style>
<style name="ShapeAppearanceOverlay.Avatar2" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">7dp</item>
</style>
<style name="ShapeAppearanceOverlay.Chip.Rounded" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSizeTopLeft">12dp</item>

View file

@ -90,6 +90,14 @@
app:summary="@string/crop_thumb_summary"
app:title="@string/crop_thumb" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/baseline_album_24"
app:key="playlist_as_album"
app:summary="@string/playlist_as_album_summary"
app:title="@string/playlist_as_album" />
</PreferenceCategory>