remove trim filenames command from command type
fixed app not showing formats when it has generic formats fixed author getting written as NA for kick videos added separate channel for the worker notification that users can turn off
This commit is contained in:
parent
2964f7da75
commit
c05224336a
23 changed files with 361 additions and 215 deletions
133
README-sq.md
Normal file
133
README-sq.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<h1 align="center">
|
||||
<img src="fastlane/metadata/android/en-US/images/icon.png" width="25%" /> <br>
|
||||
YTDLnis
|
||||
</h1>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.MD">English</a>
|
||||
|
|
||||
Shqip
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
YTDLnis është një shkarkues video/audio falas dhe me burim të hapur duke përdorur yt-dlp për Android 6.0 dhe më lart.
|
||||
</h3>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/deniscerri/ytdlnis/releases/latest)
|
||||
[](https://android.izzysoft.de/repo/apk/com.deniscerri.ytdl)
|
||||
[](https://ytdlnis.en.uptodown.com/android/download)
|
||||
|
||||

|
||||
[](https://github.com/deniscerri/ytdlnis/releases)
|
||||
[](https://github.com/deniscerri/ytdlnis/releases)
|
||||
[](https://hosted.weblate.org/engage/ytdlnis/?utm_source=widget)
|
||||
[](https://discord.gg/WW3KYWxAPm)
|
||||
[](https://t.me/ytdlnis)
|
||||
[](https://t.me/ytdlnisupdates)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
## 💡 Veçoritë:
|
||||
|
||||
- shkarko skedarë audio/video nga më shumë se <a href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md">1000 website</a>
|
||||
- përpunoni lista
|
||||
- modifikoni çdo artikull të listës veç e veç si me një artikull normal shkarkimi.
|
||||
- zgjidhni një format të përbashkët për të gjithë artikujt dhe/ose zgjidhni formate të shumta audio në rast se po i shkarkoni si video
|
||||
- zgjidhni një lloj shkarkimi për të gjithë artikujt
|
||||
- zgjidhni një shabllon të emrit të skedarit për të gjithë artikujt
|
||||
- përditëso llojin i shkarkimit në audio/video/komandë pme një klik
|
||||
- vendosni shkarkimet në radhë dhe planifikoni ato sipas datës dhe orës
|
||||
- gjithashtu mund të planifikoni shumë artikuj në të njëjtën kohë
|
||||
- shkarkoni shumë artikuj në të njëjtën kohë
|
||||
- përdorni komanda dhe shabllone të personalizuara ose shkoni në modalitetin e plotë yt-dlp me një terminal të integruar
|
||||
- mund të ruani dhe riktheni shabllonet, në mënyrë që të mund t'i ndani me miqtë tuaj
|
||||
- Mbështetje për BISKOTAT. Hyni me llogaritë tuaja dhe shkarkoni video private/të padisponueshme, zhbllokoni formatet premium etj.
|
||||
- prit video bazuar sipas kohës dhe kapitujt e videos (Kjo veçori yt-dlp është eksperimentale në projektin origjinal)
|
||||
- ju mund të bëni prerje të pafundme
|
||||
- hiqni elementet e SPONSORBLOCK (si pjeset pa muzike) nga artikulli
|
||||
- futini ato si kapituj në videon tuaj
|
||||
- fut titrat/metadatat/kapitujt etj
|
||||
- modifikoni titullin dhe autorin
|
||||
- ndani artikullin në skedarë të veçantë në varësi të kapitujve të tij
|
||||
- zgjidhni formate të ndryshme shkarkimi
|
||||
- shfaq karte direkt nga menyja e shpërndarjes, nuk ka nevojë të hapni aplikacionin
|
||||
- mund të krijoni një skedar txt dhe ta plotësoni me linke/playliste/fjale kerkimi të ndara nga një rresht i ri dhe aplikacioni do t'i përpunojë ato
|
||||
- kërkoni ose futni një link nga aplikacioni
|
||||
- mund të grumbulloni kërkime në mënyrë që t'i përpunoni ato në të njëjtën kohë
|
||||
- regjistrimet e shkarkimeve në rast problemesh
|
||||
- rishkarkimi i shkarkimeve të anuluara ose të dështuara
|
||||
- mund të përdorni gjeste për të rrëshqitur majtas për ta shkarkuar përsëri dhe djathtas për t'i fshirë
|
||||
- mund të klikoni gjatë butonin e rishkarkimit në fletën e detajeve për të shfaqur kartën e shkarkimit për më shumë funksionalitet
|
||||
- INCOGNITO kur nuk dëshironi të ruani një histori shkarkimi ose regjistra
|
||||
- mënyra e shkarkimit të shpejtë
|
||||
- shkarkoni menjëherë pa pasur nevojë të prisni që të dhënat të përpunohen. Fikni kartën e poshtme dhe ajo do të fillojë menjëherë
|
||||
- hapni / ndani skedarët e shkarkuar menjëherë nga njoftimi i përfunduar
|
||||
- shumica e veçorive yt-dlp janë zbatuar, sugjerimet janë të mirëseardhura
|
||||
- ndërfaqja e MATERIAL U
|
||||
- opsionet ngjyrash
|
||||
- rezervoni dhe rivendosni veçoritë e aplikacionit. (Pothuajse gjithçka mund të rezervohet dhe mund te shperndash me shoke)
|
||||
- Arkitektura MVVM me WorkManager
|
||||
|
||||
## 📲 Fotot
|
||||
|
||||
<div>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/01.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/02.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/03.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/04.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/05.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/06.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/07.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/08.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/09.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/10.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/11.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/12.png" width="30%" />
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/13.png" width="90%" />
|
||||
</div>
|
||||
|
||||
## 💬 Kontakt
|
||||
|
||||
Bashkohuni me [Kanalin tonë Telegram](https://t.me/ytdlnis) ose [Diskord](https://discord.gg/WW3KYWxAPm) për diskutim, njoftime dhe publikime!
|
||||
|
||||
## 🔑 Lidhu me ReVanced
|
||||
|
||||
Emri i paketës së aplikacionit është "com.deniscerri.ytdl"
|
||||
|
||||
## 📝 Ndihmo për të përkthyer në Weblate
|
||||
<a href="https://hosted.weblate.org/engage/ytdlnis/">
|
||||
<img src="https://hosted.weblate.org/widgets/ytdlnis/-/strings/open-graph.png" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/ytdlnis/">
|
||||
<img src="https://hosted.weblate.org/widgets/ytdlnis/-/multi-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
## 😇 Kontributo
|
||||
|
||||
Nëse dëshironi të kontribuoni. Ju lutemi lexoni seksionin [Kontribimi](CONTRIBUTING.MD).
|
||||
|
||||
## 📄 Liçensa
|
||||
|
||||
[GNU GPL v3.0](https://github.com/deniscerri/ytdlnis/blob/main/LICENSE)
|
||||
|
||||
⚠️ Paralajmërim <br>
|
||||
|
||||
Përveç kodit burimor të licencuar sipas licencës GPLv3, të gjitha palët e tjera janë të ndaluara të përdorin emrin 'YTDLnis' si një aplikacion shkarkues dhe e njëjta gjë vlen edhe për derivatet e tij. Derivatet përfshijnë por nuk kufizohen në forks dhe ndërtime jozyrtare.
|
||||
|
||||
## 🙏 Falenderimet
|
||||
|
||||
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) dhe kontribuesit e tij për të bërë të mundur këtë mjet. Pa të ky aplikacion nuk do të ekzistonte.
|
||||
- [youtubedl-android](https://github.com/yausername/youtubedl-android) për transferimin e yt-dlp në android.
|
||||
- [dvd](https://github.com/yausername/dvd) për të treguar se si të zbatohet youtubedl-android
|
||||
- [seal](https://github.com/JunkFood02/Seal) për elementë dhe veçori të caktuara të dizajnit doja të përdorja gjithashtu në këtë aplikacion
|
||||
- [decipher3114](https://github.com/decipher3114) për ikonën e aplikacionit
|
||||
|
||||
dhe shumë të huaj të tjerë nga interneti.
|
||||
|
|
@ -6,6 +6,8 @@
|
|||
<div align="center">
|
||||
English
|
||||
|
|
||||
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-sq.md">Shqip</a>
|
||||
|
|
||||
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-az.md">Azərbaycanca</a>
|
||||
|
|
||||
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-pt.md">Português</a>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
lintOptions{
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
android.applicationVariants.configureEach { variant ->
|
||||
variant.outputs.each { output ->
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
</activity>
|
||||
<activity
|
||||
android:name=".ui.more.settings.SettingsActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<intent-filter>
|
||||
|
|
|
|||
|
|
@ -198,6 +198,12 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
true
|
||||
}
|
||||
//settings button
|
||||
(navigationView as NavigationView).menu.getItem(8).setOnMenuItemClickListener {
|
||||
val intent = Intent(context, SettingsActivity::class.java)
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
|
||||
(navigationView as NavigationView).getHeaderView(0).findViewById<TextView>(R.id.title).text = ThemeUtil.getStyledAppName(this)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,14 +122,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
|
||||
val confTmp = Configuration(application.resources.configuration)
|
||||
confTmp.locale = Locale(sharedPreferences.getString("app_language", "en")!!)
|
||||
confTmp.setLocale(Locale(sharedPreferences.getString("app_language", "en")!!))
|
||||
val metrics = DisplayMetrics()
|
||||
resources = Resources(application.assets, metrics, confTmp)
|
||||
|
||||
|
||||
videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||
defaultVideoFormats = getGenericVideoFormats()
|
||||
bestVideoFormat = defaultVideoFormats.last()
|
||||
defaultVideoFormats = infoUtil.getGenericVideoFormats(resources)
|
||||
bestVideoFormat = defaultVideoFormats.first()
|
||||
|
||||
audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||
bestAudioFormat = if (audioFormatIDPreference.isEmpty()){
|
||||
|
|
@ -387,10 +387,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
return cloneFormat (
|
||||
try {
|
||||
try{
|
||||
formats.filter { it.format_note.contains("audio", ignoreCase = true)}.sortedBy { it.format_id }
|
||||
formats.filter { it.format_note.contains("audio", ignoreCase = true)}
|
||||
.firstOrNull { audioFormatIDPreference.indexOfFirst { a -> a.contains(it.format_id) } >= 0 || it.container == audioContainer} ?: throw Exception()
|
||||
}catch (e: Exception){
|
||||
formats.last { it.format_note.contains("audio", ignoreCase = true) }
|
||||
formats.first { it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
}catch (e: Exception){
|
||||
bestAudioFormat
|
||||
|
|
@ -401,18 +401,17 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
Type.video -> {
|
||||
return cloneFormat(
|
||||
try {
|
||||
val theFormats = formats.filter { !it.format_note.contains("audio", true) }.sortedBy { it.format_id }.ifEmpty { defaultVideoFormats }.sortedByDescending { it.filesize }
|
||||
val theFormats = formats.filter { !it.format_note.contains("audio", true) }.ifEmpty { defaultVideoFormats.sortedByDescending { it.filesize } }
|
||||
when (videoQualityPreference) {
|
||||
"worst" -> {
|
||||
theFormats.first()
|
||||
theFormats.last()
|
||||
}
|
||||
else /*best*/ -> {
|
||||
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
|
||||
requirements.add { it: Format -> formatIDPreference.sorted().contains(it.format_id) }
|
||||
requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
|
||||
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
|
||||
requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) }
|
||||
|
||||
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.split("_")[0].dropLast(1)) }
|
||||
requirements.add { it: Format -> "^(${videoCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.vcodec)}
|
||||
theFormats.maxByOrNull { f -> requirements.count{ req -> req(f)} } ?: throw Exception()
|
||||
}
|
||||
}
|
||||
|
|
@ -440,23 +439,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
)
|
||||
}
|
||||
|
||||
fun getGenericAudioFormats() : MutableList<Format>{
|
||||
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("audio_format", "")
|
||||
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"","", "",1, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
fun getGenericVideoFormats() : MutableList<Format>{
|
||||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("video_format", "")
|
||||
videoFormats.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it.split("_")[0])) }
|
||||
formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",1, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
fun getLatestCommandTemplateAsFormat() : Format {
|
||||
val t = commandTemplateDao.getFirst()
|
||||
|
|
|
|||
|
|
@ -2,11 +2,6 @@ package com.deniscerri.ytdlnis.ui
|
|||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
|||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private suspend fun updateSearchViewItems(it: Editable?, linkYouCopied: View?){
|
||||
private suspend fun updateSearchViewItems(searchQuery: Editable?, linkYouCopied: View?){
|
||||
searchSuggestionsLinearLayout!!.visibility = GONE
|
||||
searchHistoryLinearLayout!!.visibility = GONE
|
||||
searchSuggestionsLinearLayout!!.removeAllViews()
|
||||
|
|
@ -449,16 +449,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
|||
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
|
||||
}
|
||||
val suggestions = withContext(Dispatchers.IO){
|
||||
if (it!!.isEmpty()) {
|
||||
resultViewModel.getSearchHistory().map { it.query }
|
||||
}else if (sharedPreferences!!.getBoolean("search_suggestions", false)){
|
||||
infoUtil!!.getSearchSuggestions(it.toString())
|
||||
resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) } +
|
||||
if (sharedPreferences!!.getBoolean("search_suggestions", false)){
|
||||
infoUtil!!.getSearchSuggestions(searchQuery.toString())
|
||||
}else{
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
if (it!!.isEmpty()){
|
||||
if (searchQuery!!.isEmpty()){
|
||||
for (i in suggestions.indices) {
|
||||
val v = LayoutInflater.from(fragmentContext)
|
||||
.inflate(R.layout.search_suggestion_item, null)
|
||||
|
|
|
|||
|
|
@ -335,7 +335,6 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
if ((event!!.action == KeyEvent.ACTION_DOWN) &&
|
||||
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
|
||||
val values = rangeSlider.values
|
||||
var endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100
|
||||
|
||||
toTextInput.editText!!.clearFocus()
|
||||
|
|
@ -361,7 +360,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
rangeSlider.setValues(startValue, endValue)
|
||||
okBtn.isEnabled = startValue != 0F || endValue != 100F
|
||||
try {
|
||||
player.seekTo(startSeconds.toLong() * 1000)
|
||||
player.seekTo((seconds.toLong() - 4L) * 1000)
|
||||
player.play()
|
||||
}catch (ignored: Exception) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import java.io.File
|
|||
|
||||
|
||||
class DownloadAudioFragment(private var resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment(), TitleAuthorSync {
|
||||
private var _binding : FragmentHomeBinding? = null
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -46,6 +45,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
private lateinit var saveDir : TextInputLayout
|
||||
private lateinit var freeSpace : TextView
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var genericAudioFormats: MutableList<Format>
|
||||
|
||||
lateinit var downloadItem : DownloadItem
|
||||
lateinit var title : TextInputLayout
|
||||
|
|
@ -55,12 +55,12 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false)
|
||||
activity = getActivity()
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||
.maxByOrNull { it.filesize }!!
|
||||
}.onFailure {
|
||||
downloadItem.format = downloadViewModel.getGenericAudioFormats().last()
|
||||
downloadItem.format = genericAudioFormats.last()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
val containerAutoCompleteTextView =
|
||||
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
||||
|
||||
if (formats.isEmpty()) formats = downloadViewModel.getGenericAudioFormats()
|
||||
if (formats.isEmpty()) formats = genericAudioFormats
|
||||
|
||||
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
||||
val chosenFormat = downloadItem.format
|
||||
|
|
@ -154,16 +154,17 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultItem.formats.removeAll(formats.toSet())
|
||||
resultItem.formats.addAll(allFormats.first())
|
||||
resultItem.formats.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) })
|
||||
resultViewModel.update(resultItem)
|
||||
}
|
||||
}
|
||||
formats = allFormats.first().toMutableList()
|
||||
formats = allFormats.first().filter { !genericAudioFormats.contains(it) }.toMutableList()
|
||||
formats.removeAll(genericAudioFormats)
|
||||
}
|
||||
}
|
||||
formatCard.setOnClickListener{
|
||||
if (parentFragmentManager.findFragmentByTag("formatSheet") == null){
|
||||
val bottomSheet = FormatSelectionBottomSheetDialog(listOf(downloadItem), listOf(formats), listener)
|
||||
val bottomSheet = FormatSelectionBottomSheetDialog(listOf(downloadItem), listOf(formats.ifEmpty { genericAudioFormats }), listener)
|
||||
bottomSheet.show(parentFragmentManager, "formatSheet")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ import kotlinx.coroutines.withContext
|
|||
import java.io.File
|
||||
|
||||
class DownloadCommandFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment() {
|
||||
private var _binding : FragmentHomeBinding? = null
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -57,7 +56,6 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
fragmentView = inflater.inflate(R.layout.fragment_download_command, container, false)
|
||||
activity = getActivity()
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
|||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
||||
|
|
@ -78,6 +79,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var bottomAppBar: BottomAppBar
|
||||
private lateinit var filesize : TextView
|
||||
|
|
@ -90,6 +92,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi", "NotifyDataSetChanged")
|
||||
|
|
@ -357,8 +360,8 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
items.map { it.allFormats }
|
||||
}else{
|
||||
when(items.first().type){
|
||||
DownloadViewModel.Type.audio -> listOf<List<Format>>(downloadViewModel.getGenericAudioFormats())
|
||||
else -> listOf<List<Format>>(downloadViewModel.getGenericVideoFormats())
|
||||
DownloadViewModel.Type.audio -> listOf<List<Format>>(infoUtil.getGenericAudioFormats(requireContext().resources))
|
||||
else -> listOf<List<Format>>(infoUtil.getGenericVideoFormats(requireContext().resources))
|
||||
}
|
||||
}
|
||||
val bottomSheet = FormatSelectionBottomSheetDialog(items, formats, formatListener)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
|
|||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
|
|
@ -39,7 +38,6 @@ import java.io.File
|
|||
|
||||
|
||||
class DownloadVideoFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment(), TitleAuthorSync {
|
||||
private var _binding : FragmentHomeBinding? = null
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -50,6 +48,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
private lateinit var saveDir : TextInputLayout
|
||||
private lateinit var freeSpace : TextView
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var genericVideoFormats: MutableList<Format>
|
||||
|
||||
lateinit var downloadItem: DownloadItem
|
||||
|
||||
|
|
@ -58,12 +57,12 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
fragmentView = inflater.inflate(R.layout.fragment_download_video, container, false)
|
||||
activity = getActivity()
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java]
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +135,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
downloadItem.allFormats.filter { it.vcodec.isNotEmpty() }
|
||||
.maxByOrNull { it.filesize }!!
|
||||
}.onFailure {
|
||||
downloadItem.format = downloadViewModel.getGenericVideoFormats().last()
|
||||
downloadItem.format = genericVideoFormats.last()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,7 +148,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
||||
if (containerPreference == "Default") containerPreference = getString(R.string.defaultValue)
|
||||
|
||||
if (formats.isEmpty()) formats = downloadViewModel.getGenericVideoFormats()
|
||||
if (formats.isEmpty()) formats = genericVideoFormats
|
||||
|
||||
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
||||
|
||||
|
|
@ -158,22 +157,24 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
val listener = object : OnFormatClickListener {
|
||||
override fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>) {
|
||||
downloadItem.format = item.first().format
|
||||
item.first().audioFormats?.map { it.format_id }
|
||||
?.let { downloadItem.videoPreferences.audioFormatIDs.addAll(it) }
|
||||
item.first().audioFormats?.map { it.format_id }?.let {
|
||||
downloadItem.videoPreferences.audioFormatIDs.clear()
|
||||
downloadItem.videoPreferences.audioFormatIDs.addAll(it)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultItem.formats.removeAll(formats.toSet())
|
||||
resultItem.formats.addAll(allFormats.first())
|
||||
resultItem.formats.removeAll(formats)
|
||||
resultItem.formats.addAll(allFormats.first().filter { !genericVideoFormats.contains(it) })
|
||||
resultViewModel.update(resultItem)
|
||||
}
|
||||
}
|
||||
formats = allFormats.first().toMutableList()
|
||||
formats = allFormats.first().filter { !genericVideoFormats.contains(it) }.toMutableList()
|
||||
UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format, item.first().audioFormats)
|
||||
}
|
||||
}
|
||||
formatCard.setOnClickListener{
|
||||
if (parentFragmentManager.findFragmentByTag("formatSheet") == null){
|
||||
val bottomSheet = FormatSelectionBottomSheetDialog(listOf(downloadItem), listOf(formats), listener)
|
||||
val bottomSheet = FormatSelectionBottomSheetDialog(listOf(downloadItem), listOf(formats.ifEmpty { genericVideoFormats }), listener)
|
||||
bottomSheet.show(parentFragmentManager, "formatSheet")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}
|
||||
}
|
||||
|
||||
for (i in finalFormats.lastIndex downTo 0){
|
||||
for (i in 0.. finalFormats.lastIndex){
|
||||
val format = finalFormats[i]
|
||||
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
||||
formatItem.tag = "${format.format_id}${format.format_note}"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
|
|
@ -29,7 +27,6 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
override val title: Int = R.string.general
|
||||
|
||||
private var language: ListPreference? = null
|
||||
private var language13: Preference? = null
|
||||
private var theme: ListPreference? = null
|
||||
private var accent: ListPreference? = null
|
||||
private var highContrast: SwitchPreferenceCompat? = null
|
||||
|
|
@ -51,45 +48,28 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
}
|
||||
|
||||
language = findPreference("app_language")
|
||||
language13 = findPreference("app_language_13")
|
||||
theme = findPreference("ytdlnis_theme")
|
||||
accent = findPreference("theme_accent")
|
||||
highContrast = findPreference("high_contrast")
|
||||
locale = findPreference("locale")
|
||||
showTerminalShareIcon = findPreference("show_terminal")
|
||||
|
||||
if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){
|
||||
val values = resources.getStringArray(R.array.language_values)
|
||||
val entries = mutableListOf<String>()
|
||||
values.forEach {
|
||||
entries.add(Locale(it).getDisplayName(Locale(it)))
|
||||
}
|
||||
language!!.entries = entries.toTypedArray()
|
||||
val values = resources.getStringArray(R.array.language_values)
|
||||
val entries = mutableListOf<String>()
|
||||
values.forEach {
|
||||
entries.add(Locale(it).getDisplayName(Locale(it)))
|
||||
}
|
||||
language!!.entries = entries.toTypedArray()
|
||||
|
||||
if(language!!.value == null) language!!.value = Locale.getDefault().language
|
||||
language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value))
|
||||
if(language!!.value == null) language!!.value = Locale.getDefault().language
|
||||
language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value))
|
||||
|
||||
language!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString()))
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
|
||||
true
|
||||
}
|
||||
|
||||
language13?.isVisible = false
|
||||
}else{
|
||||
language?.isVisible = false
|
||||
language13?.isVisible = true
|
||||
language13?.summary = Locale.getDefault().displayLanguage
|
||||
|
||||
language13!!.setOnPreferenceClickListener {
|
||||
val intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
|
||||
intent.data = Uri.fromParts("package", requireActivity().packageName, null)
|
||||
requireActivity().startActivity(intent)
|
||||
requireActivity().finishAffinity()
|
||||
language!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString()))
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
theme!!.summary = theme!!.entry
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package com.deniscerri.ytdlnis.util
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.os.Looper
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.widget.Toast
|
||||
import androidx.preference.AndroidResources
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||
|
|
@ -32,7 +34,6 @@ import java.util.regex.Pattern
|
|||
|
||||
|
||||
class InfoUtil(private val context: Context) {
|
||||
private var items: ArrayList<ResultItem?>
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
|
|
@ -43,12 +44,10 @@ class InfoUtil(private val context: Context) {
|
|||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
items = ArrayList()
|
||||
}
|
||||
|
||||
|
||||
fun search(query: String): ArrayList<ResultItem?> {
|
||||
items = ArrayList()
|
||||
return when(sharedPreferences.getString("search_engine", "ytsearch")){
|
||||
"ytsearch" -> try{
|
||||
searchFromPiped(query)
|
||||
|
|
@ -68,6 +67,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
val data = genericRequest("$pipedURL/search?q=$query&filter=videos®ion=${countryCODE}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||
|
|
@ -86,6 +86,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun searchFromPipedMusic(query: String): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
val data = genericRequest("$pipedURL/search?q=$query=&filter=music_songs®ion=${countryCODE}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||
|
|
@ -105,7 +106,7 @@ class InfoUtil(private val context: Context) {
|
|||
@Throws(JSONException::class)
|
||||
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
||||
try{
|
||||
items = ArrayList()
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
// -------------- PIPED API FUNCTION -------------------
|
||||
var url = ""
|
||||
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
||||
|
|
@ -226,7 +227,6 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
}
|
||||
formats.sortBy { it.filesize }
|
||||
formats.groupBy { it.format_id }.forEach {
|
||||
if (it.value.count() > 1) {
|
||||
it.value.filter { f-> !f.format_note.contains("original", true) }.forEachIndexed { index, format -> format.format_id = format.format_id.split("-")[0] + "-${index}" }
|
||||
|
|
@ -234,6 +234,7 @@ class InfoUtil(private val context: Context) {
|
|||
engDefault?.format_id = (engDefault?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}"
|
||||
}
|
||||
}
|
||||
formats.sortByDescending { it.filesize }
|
||||
}
|
||||
|
||||
val chapters = ArrayList<ChapterItem>()
|
||||
|
|
@ -350,7 +351,6 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getTrending(): ArrayList<ResultItem?> {
|
||||
items = ArrayList()
|
||||
if (sharedPreferences.getString("api_key", "")!!.isNotBlank()){
|
||||
return getTrendingFromYoutubeAPI()
|
||||
}
|
||||
|
|
@ -359,8 +359,8 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getTrendingFromYoutubeAPI(): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
val key = sharedPreferences.getString("api_key", "")!!
|
||||
|
||||
val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10®ionCode=$countryCODE&maxResults=25&key=$key"
|
||||
//short data
|
||||
val res = genericRequest(url)
|
||||
|
|
@ -389,6 +389,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
private fun getTrendingFromPiped(): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
val url = "$pipedURL/trending?region=$countryCODE"
|
||||
val res = genericArrayRequest(url)
|
||||
try {
|
||||
|
|
@ -547,7 +548,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
||||
items = ArrayList()
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||
try {
|
||||
val request : YoutubeDLRequest
|
||||
|
|
@ -583,12 +584,12 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||
val results: Array<String?> = try {
|
||||
val results: List<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
youtubeDLResponse.out.split(lineSeparator!!).toTypedArray()
|
||||
youtubeDLResponse.out.split(lineSeparator!!)
|
||||
} catch (e: Exception) {
|
||||
arrayOf(youtubeDLResponse.out)
|
||||
}
|
||||
listOf(youtubeDLResponse.out)
|
||||
}.filter { it.isNotBlank() }
|
||||
for (result in results) {
|
||||
if (result.isNullOrBlank()) continue
|
||||
val jsonObject = JSONObject(result)
|
||||
|
|
@ -690,7 +691,7 @@ class InfoUtil(private val context: Context) {
|
|||
val formats = arrayListOf<Format>()
|
||||
|
||||
if (formatsInJSON != null) {
|
||||
for (f in 0 until formatsInJSON.length()){
|
||||
for (f in formatsInJSON.length() - 1 downTo 0){
|
||||
val format = formatsInJSON.getJSONObject(f)
|
||||
if (format.has("filesize")){
|
||||
if (format.get("filesize") == "None"){
|
||||
|
|
@ -987,8 +988,9 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
val limitRate = sharedPreferences.getString("limit_rate", "")
|
||||
if (limitRate != "") request.addOption("-r", limitRate!!)
|
||||
request.addOption("--trim-filenames", 120)
|
||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||
request.addOption("--trim-filenames", 120)
|
||||
|
||||
if (downloadItem.SaveThumb) {
|
||||
request.addOption("--write-thumbnail")
|
||||
request.addOption("--convert-thumbnails", "jpg")
|
||||
|
|
@ -1024,6 +1026,7 @@ class InfoUtil(private val context: Context) {
|
|||
request.addCommands(listOf("--replace-in-metadata", "title", ".+", downloadItem.title))
|
||||
}
|
||||
if (downloadItem.author.isNotBlank()){
|
||||
request.addOption("--parse-metadata", "%(artist,uploader,creator,channel|null)s:%(uploader)s")
|
||||
request.addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author))
|
||||
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
||||
}
|
||||
|
|
@ -1078,7 +1081,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader,channel)s")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("restrict_filenames", true)) {
|
||||
|
|
@ -1166,10 +1169,6 @@ class InfoUtil(private val context: Context) {
|
|||
DownloadViewModel.Type.video -> {
|
||||
val supportedContainers = context.resources.getStringArray(R.array.video_containers)
|
||||
|
||||
if (!sharedPreferences.getBoolean("embed_metadata", true)){
|
||||
request.addOption("--no-embed-metadata")
|
||||
}
|
||||
|
||||
if (downloadItem.videoPreferences.addChapters) {
|
||||
if (sharedPreferences.getBoolean("use_sponsorblock", true)){
|
||||
request.addOption("--sponsorblock-mark", "all")
|
||||
|
|
@ -1200,100 +1199,102 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
//format logic
|
||||
|
||||
val defaultFormats = context.resources.getStringArray(R.array.video_formats_values)
|
||||
var usingGenericFormat = false
|
||||
|
||||
var videof = downloadItem.format.format_id
|
||||
if (videof == context.resources.getString(R.string.best_quality) || videof == "best") {
|
||||
videof = "bestvideo"
|
||||
usingGenericFormat = true
|
||||
}else if (videof == context.resources.getString(R.string.worst_quality) || videof == "worst") {
|
||||
videof = "worst"
|
||||
}else if (defaultFormats.contains(videof)) {
|
||||
videof = "bestvideo[height<="+videof.split("_")[0].dropLast(1)+"]"
|
||||
}
|
||||
|
||||
val preferredFormatIDs = sharedPreferences.getString("format_id", "").toString()
|
||||
.split(",")
|
||||
.filter { it.isNotEmpty() }
|
||||
.ifEmpty { listOf(videof) }
|
||||
|
||||
var audiof = "bestaudio"
|
||||
var audiof = "ba"
|
||||
if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()){
|
||||
audiof = downloadItem.videoPreferences.audioFormatIDs.joinToString("+")
|
||||
}
|
||||
if (downloadItem.videoPreferences.removeAudio) audiof = ""
|
||||
|
||||
val preferredAudioFormatIDs = sharedPreferences.getString("format_id_audio", "")
|
||||
.toString()
|
||||
.split(",")
|
||||
.filter { it.isNotEmpty() }
|
||||
.ifEmpty { listOf(audiof) }
|
||||
|
||||
val preferredVideoFormats = if (usingGenericFormat) preferredFormatIDs else listOf(videof)
|
||||
val preferredAudioFormats = if (usingGenericFormat && downloadItem.videoPreferences.audioFormatIDs.isEmpty()) {
|
||||
preferredAudioFormatIDs
|
||||
} else listOf(audiof)
|
||||
|
||||
if (preferredAudioFormats.any{it.contains("+")}){
|
||||
request.addOption("--audio-multistreams")
|
||||
}
|
||||
|
||||
val preferredCodec = sharedPreferences.getString("video_codec", "")
|
||||
val extPref = if (cont.isNotBlank()) "[ext=$cont]" else ""
|
||||
val vCodecPref = if (preferredCodec!!.isNotBlank()) "[vcodec^=$preferredCodec]" else ""
|
||||
|
||||
val defaultFormats = context.resources.getStringArray(R.array.video_formats_values)
|
||||
val usingGenericFormat = defaultFormats.contains(videof) || downloadItem.allFormats.isEmpty() || downloadItem.allFormats == getGenericVideoFormats(context.resources)
|
||||
val f = StringBuilder()
|
||||
|
||||
//build format with extension and vcodec
|
||||
preferredVideoFormats.forEach { v ->
|
||||
preferredAudioFormats.forEach { a ->
|
||||
val aa = if (a.isNotBlank()) "+$a" else ""
|
||||
f.append("$v$extPref$vCodecPref$aa/")
|
||||
}
|
||||
}
|
||||
if(!usingGenericFormat){
|
||||
val aa = if (audiof.isNotBlank()) "+$audiof" else ""
|
||||
f.append("$videof$aa/$videof/best")
|
||||
|
||||
//build format with vcodec
|
||||
if (extPref.isNotBlank()){
|
||||
preferredVideoFormats.forEach { v ->
|
||||
if (audiof.contains("+")){
|
||||
request.addOption("--audio-multistreams")
|
||||
}
|
||||
}else{
|
||||
if (videof == context.resources.getString(R.string.best_quality) || videof == "best") {
|
||||
videof = "bv"
|
||||
}else if (videof == context.resources.getString(R.string.worst_quality) || videof == "worst") {
|
||||
videof = "worst"
|
||||
}else if (defaultFormats.contains(videof)) {
|
||||
videof = "bv[height<="+videof.split("_")[0].dropLast(1)+"]"
|
||||
}
|
||||
|
||||
val preferredFormatIDs = sharedPreferences.getString("format_id", "").toString()
|
||||
.split(",")
|
||||
.filter { it.isNotEmpty() }
|
||||
.ifEmpty { listOf(videof) }.toMutableList()
|
||||
if (!preferredFormatIDs.contains(videof)){
|
||||
preferredFormatIDs.add(0, videof)
|
||||
}
|
||||
|
||||
val preferredAudioFormatIDs = sharedPreferences.getString("format_id_audio", "")
|
||||
.toString()
|
||||
.split(",")
|
||||
.filter { it.isNotEmpty() }
|
||||
.ifEmpty { listOf(audiof) }
|
||||
|
||||
val preferredAudioFormats = if (downloadItem.videoPreferences.audioFormatIDs.isEmpty()) {
|
||||
preferredAudioFormatIDs
|
||||
} else listOf(audiof)
|
||||
|
||||
if (preferredAudioFormats.any{it.contains("+")}){
|
||||
request.addOption("--audio-multistreams")
|
||||
}
|
||||
|
||||
val preferredCodec = sharedPreferences.getString("video_codec", "")
|
||||
val extPref = if (cont.isNotBlank()) "[ext=$cont]" else ""
|
||||
val vCodecPref = if (preferredCodec!!.isNotBlank()) "[vcodec~='^($preferredCodec)']" else ""
|
||||
|
||||
|
||||
preferredFormatIDs.forEach { v ->
|
||||
//build format with extension and vcodec
|
||||
preferredAudioFormats.forEach { a ->
|
||||
val aa = if (a.isNotBlank()) "+$a" else ""
|
||||
f.append("$videof$vCodecPref$aa/")
|
||||
f.append("$v$extPref$vCodecPref$aa/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vCodecPref.isNotBlank()){
|
||||
preferredVideoFormats.forEach { v ->
|
||||
//build format with vcodec
|
||||
preferredAudioFormats.forEach {a ->
|
||||
val aa = if (a.isNotBlank()) "+$a" else ""
|
||||
f.append("$v$aa/")
|
||||
if (extPref.isNotBlank()){
|
||||
preferredAudioFormats.forEach { a ->
|
||||
val aa = if (a.isNotBlank()) "+$a" else ""
|
||||
f.append("$v$vCodecPref$aa/")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!downloadItem.videoPreferences.removeAudio){
|
||||
//build format with best audio
|
||||
preferredVideoFormats.forEach { v ->
|
||||
if(!f.contains("$v+bestaudio/")){
|
||||
f.append("$v+bestaudio/")
|
||||
//build format with audio
|
||||
if (vCodecPref.isNotBlank()){
|
||||
preferredAudioFormats.forEach {a ->
|
||||
val aa = if (a.isNotBlank()) "+$a" else ""
|
||||
f.append("$v$aa/")
|
||||
}
|
||||
}
|
||||
|
||||
if (!downloadItem.videoPreferences.removeAudio){
|
||||
//build format with best audio
|
||||
if(!f.contains("$v+ba/")){
|
||||
f.append("$v+ba/")
|
||||
}
|
||||
|
||||
//build formats with standalone video
|
||||
if(!f.contains("/$v/")){
|
||||
f.append("$v/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//build formats with standalone video
|
||||
preferredVideoFormats.forEach { v ->
|
||||
if(!f.contains("/$v/")){
|
||||
f.append("$v/")
|
||||
}
|
||||
if(!f.endsWith("best/")){
|
||||
//last fallback
|
||||
f.append("best")
|
||||
}
|
||||
}
|
||||
|
||||
if(!f.endsWith("best/")){
|
||||
//last fallback
|
||||
f.append("best")
|
||||
}
|
||||
|
||||
request.addOption("-f", f.toString().replace("/$".toRegex(), ""))
|
||||
|
||||
|
|
@ -1302,8 +1303,10 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
request.addOption("--write-subs")
|
||||
request.addOption("--write-auto-subs")
|
||||
request.addOption("--sub-format", "${subFormat}/best")
|
||||
request.addOption("--convert-subtitles", subFormat ?: "srt")
|
||||
if(subFormat!!.isNotBlank()){
|
||||
request.addOption("--sub-format", "${subFormat}/best")
|
||||
request.addOption("--convert-subtitles", subFormat ?: "srt")
|
||||
}
|
||||
if (!downloadItem.videoPreferences.embedSubs) {
|
||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
||||
}
|
||||
|
|
@ -1368,6 +1371,27 @@ class InfoUtil(private val context: Context) {
|
|||
return final
|
||||
}
|
||||
|
||||
fun getGenericAudioFormats(resources: Resources) : MutableList<Format>{
|
||||
val audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("audio_format", "")
|
||||
audioFormats.reversed().forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"","", "",1, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
fun getGenericVideoFormats(resources: Resources) : MutableList<Format>{
|
||||
val formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
|
||||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("video_format", "")
|
||||
videoFormats.reversed().forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it.split("_")[0])) }
|
||||
formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",1, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
private val pipedURL = sharedPreferences.getString("piped_instance", defaultPipedURL)?.ifEmpty { defaultPipedURL }?.removeSuffix("/")
|
||||
|
||||
class PlaylistTuple internal constructor(
|
||||
|
|
|
|||
|
|
@ -34,11 +34,18 @@ class NotificationUtil(var context: Context) {
|
|||
NotificationManager::class.java
|
||||
)
|
||||
|
||||
//gui downloads
|
||||
var name: CharSequence = context.getString(R.string.download_notification_channel_name)
|
||||
var description = context.getString(R.string.download_notification_channel_description)
|
||||
//downloading worker notification
|
||||
var name: CharSequence = context.getString(R.string.downloading)
|
||||
var description = "WorkManager Default Notification"
|
||||
val importance = NotificationManager.IMPORTANCE_LOW
|
||||
var channel = NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||
var channel = NotificationChannel(DOWNLOAD_WORKER_CHANNEL_ID, name, importance)
|
||||
channel.description = description
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
//gui downloads
|
||||
name = context.getString(R.string.download_notification_channel_name)
|
||||
description = context.getString(R.string.download_notification_channel_description)
|
||||
channel = NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||
channel.description = description
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
|
|
@ -77,7 +84,7 @@ class NotificationUtil(var context: Context) {
|
|||
}
|
||||
|
||||
fun createDefaultWorkerNotification() : Notification {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_WORKER_CHANNEL_ID)
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(context.getString(R.string.downloading))
|
||||
|
|
@ -480,6 +487,7 @@ class NotificationUtil(var context: Context) {
|
|||
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"
|
||||
private const val PROGRESS_MAX = 100
|
||||
private const val PROGRESS_CURR = 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,16 +57,13 @@ class UpdateUtil(var context: Context) {
|
|||
v = res.first { !it.tag_name.contains("beta", true) }
|
||||
}
|
||||
|
||||
val current = BuildConfig.VERSION_NAME.replace("-beta", "").replace(".", "").toInt()
|
||||
val incoming = v.tag_name.removePrefix("v").replace("-beta", "").replace(".", "").toInt()
|
||||
val current = BuildConfig.VERSION_NAME.replace("-beta", "").replace(".", "").padEnd(10,'0').toInt()
|
||||
val incoming = v.tag_name.removePrefix("v").replace("-beta", "").replace(".", "").padEnd(10,'0').toInt()
|
||||
|
||||
Log.e("aa", current.toString())
|
||||
Log.e("aa", incoming.toString())
|
||||
|
||||
var isInLatest = true
|
||||
if ((BuildConfig.VERSION_NAME.contains("beta", true) && !useBeta) ||
|
||||
(!BuildConfig.VERSION_NAME.contains("beta", true) && useBeta) ||
|
||||
current < incoming ){
|
||||
if ((current < incoming) ||
|
||||
(current > incoming && BuildConfig.VERSION_NAME.contains("beta", true) && !useBeta)){
|
||||
isInLatest = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.deniscerri.ytdlnis.work
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
|
|
@ -35,6 +38,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
|
|
@ -60,6 +64,12 @@ class DownloadWorker(
|
|||
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
||||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
||||
|
||||
val confTmp = Configuration(context.resources.configuration)
|
||||
val currLang = sharedPreferences.getString("app_language", "en")!!.split("-")
|
||||
confTmp.setLocale(if (currLang.size == 1) Locale(currLang[0]) else Locale(currLang[0], currLang[1]))
|
||||
val metrics = DisplayMetrics()
|
||||
val resources = Resources(context.assets, metrics, confTmp)
|
||||
|
||||
val pendingIntent = NavDeepLinkBuilder(context)
|
||||
.setGraph(R.navigation.nav_graph)
|
||||
.setDestination(R.id.downloadQueueMainFragment)
|
||||
|
|
@ -162,8 +172,10 @@ class DownloadWorker(
|
|||
.sortedByDescending { it.length() }
|
||||
.map { it.absolutePath }
|
||||
.toList()
|
||||
|
||||
FileUtil.scanMedia(finalPaths, context)
|
||||
if (finalPaths.isEmpty()){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}else{
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
|
||||
<string-array name="video_formats_values">
|
||||
<item>worst</item>
|
||||
<item>360p</item>
|
||||
<item>360p_ytdlnisgeneric</item>
|
||||
<item>480p_ytdlnisgeneric</item>
|
||||
<item>720p_ytdlnisgeneric</item>
|
||||
<item>1080p_ytdlnisgeneric</item>
|
||||
|
|
@ -766,6 +766,15 @@
|
|||
</array>
|
||||
|
||||
<string-array name="sub_formats">
|
||||
<item>@string/defaultValue</item>
|
||||
<item>srt</item>
|
||||
<item>ass</item>
|
||||
<item>lrc</item>
|
||||
<item>vtt</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="sub_formats_values">
|
||||
<item></item>
|
||||
<item>srt</item>
|
||||
<item>ass</item>
|
||||
<item>lrc</item>
|
||||
|
|
@ -794,9 +803,9 @@
|
|||
|
||||
<string-array name="video_codec_values">
|
||||
<item></item>
|
||||
<item>av0</item>
|
||||
<item>vp</item>
|
||||
<item>avc</item>
|
||||
<item>hev</item>
|
||||
<item>av[01]</item>
|
||||
<item>vp[09]</item>
|
||||
<item>avc|h264</item>
|
||||
<item>hev|h265</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
|
@ -292,7 +292,7 @@
|
|||
<string name="extra_command">Extra Command</string>
|
||||
<string name="use_cookies">Use Cookies</string>
|
||||
<string name="embed_metadata">Embed Metadata</string>
|
||||
<string name="embed_metadata_summary">Embed metadata to the video file. Also embeds chapters/infojson if present</string>
|
||||
<string name="embed_metadata_summary">Embed and parse metadata to the audio file.</string>
|
||||
<string name="update_formats_background">Update Formats in Background</string>
|
||||
<string name="auto_update_ytdlp">Auto-Update yt-dlp</string>
|
||||
<string name="use_sponsorblock">Use SponsorBlock</string>
|
||||
|
|
|
|||
|
|
@ -11,13 +11,6 @@
|
|||
app:summary="en"
|
||||
app:title="@string/language" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/baseline_translate_24"
|
||||
app:key="app_language_13"
|
||||
app:summary="en"
|
||||
app:title="@string/language" />
|
||||
|
||||
|
||||
<ListPreference
|
||||
android:entries="@array/themes"
|
||||
android:entryValues="@array/themes_values"
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
<ListPreference
|
||||
android:defaultValue="srt"
|
||||
android:entries="@array/sub_formats"
|
||||
android:entryValues="@array/sub_formats"
|
||||
android:entryValues="@array/sub_formats_values"
|
||||
android:icon="@drawable/baseline_format_quote_24"
|
||||
app:key="sub_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
|
|
|
|||
Loading…
Reference in a new issue