alot of stuff

added remux-video for audio
made copy url similar to history card
fixed padding on cards
fixed configured download sheet not scrolling
implemented chapter suggestions in cuts
pull/79/head
deniscerri 3 years ago
parent 9f0d8ab727
commit f396787b65
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -22,7 +22,7 @@ android {
keyPassword properties["signingConfig.keyPassword"]
}
}
compileSdkVersion "android-UpsideDownCake"
compileSdkVersion 33
defaultConfig {
applicationId "com.deniscerri.ytdl"
minSdkVersion 23
@ -119,7 +119,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appCompatVer"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation 'com.google.android.material:material:1.10.0-alpha01'
implementation 'com.google.android.material:material:1.9.0-beta01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.core:core:1.9.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0'

@ -0,0 +1,7 @@
package com.deniscerri.ytdlnis.database.models
data class ChapterItem(
var start_time: Long,
var end_time: Long,
var title: String,
)

@ -4,16 +4,20 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.*
import android.content.Context.CLIPBOARD_SERVICE
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.InputType
import android.util.Log
import android.view.*
import android.view.View.*
import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.text.TextUtilsCompat
import androidx.core.view.ViewCompat
import androidx.core.view.children
@ -42,6 +46,7 @@ import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.search.SearchBar
@ -350,6 +355,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = searchView.editText.text
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
queriesChipGroup!!.removeView(chip)
}

@ -4,14 +4,17 @@ import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
@ -21,6 +24,8 @@ 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.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.tabs.TabLayout
@ -36,7 +41,10 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var commandTemplateDao: CommandTemplateDao
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
private lateinit var uiUtil: UiUtil
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -44,7 +52,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
onDownloadItemUpdateListener = listener
uiUtil = UiUtil(FileUtil())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -58,20 +66,31 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.configure_download_bottom_sheet, null)
dialog.setContentView(view)
//view.minimumHeight = resources.displayMetrics.heightPixels
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels - 700
}
tabLayout = view.findViewById(R.id.download_tablayout)
viewPager2 = view.findViewById(R.id.download_viewpager)
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, downloadItem), DownloadVideoFragment(resultItem, downloadItem))
(viewPager2.getChildAt(0) as? RecyclerView)?.apply {
isNestedScrollingEnabled = false
overScrollMode = View.OVER_SCROLL_NEVER
}
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, downloadItem), DownloadVideoFragment(resultItem, downloadItem))
var commandTemplateNr = 0
lifecycleScope.launch{
withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber()
if(nr > 0){
commandTemplateNr = commandTemplateDao.getTotalNumber()
if(commandTemplateNr > 0){
fragments.add(DownloadCommandFragment(resultItem, downloadItem))
}else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
}
}
@ -107,7 +126,12 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
viewPager2.currentItem = tab!!.position
if (tab!!.position == 2 && commandTemplateNr == 0){
tabLayout.selectTab(tabLayout.getTabAt(1))
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
viewPager2.setCurrentItem(tab.position, false)
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
@ -147,6 +171,16 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
dismiss()
}
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.text = resultItem.url
link.setOnClickListener{
uiUtil.openLinkIntent(requireContext(), resultItem.url, null)
}
link.setOnLongClickListener{
uiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
true
}
}

@ -3,6 +3,8 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.util.DisplayMetrics
@ -13,8 +15,10 @@ import android.view.View
import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.lifecycle.lifecycleScope
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
@ -31,15 +35,19 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.RangeSlider
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.reflect.Type
import java.util.*
import kotlin.properties.Delegates
@ -58,12 +66,17 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
private lateinit var toTextInput : TextInputLayout
private lateinit var cancelBtn : Button
private lateinit var okBtn : Button
private lateinit var suggestedChips: ChipGroup
private lateinit var suggestedChapters : LinearLayout
private lateinit var cutListSection : LinearLayout
private lateinit var newCutBtn : Button
private lateinit var resetBtn : Button
private lateinit var chipGroup : ChipGroup
private var timeSeconds by Delegates.notNull<Int>()
private lateinit var chapters: List<ChapterItem>
private lateinit var selectedCuts: MutableList<String>
override fun onCreate(savedInstanceState: Bundle?) {
@ -92,6 +105,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
val videoView = view.findViewById<PlayerView>(R.id.video_view)
videoView.player = player
timeSeconds = convertStringToTimestamp(item.duration)
chapters = emptyList()
//cut section
cutSection = view.findViewById(R.id.cut_section)
@ -101,12 +115,25 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
toTextInput = view.findViewById(R.id.to_textinput)
cancelBtn = view.findViewById(R.id.cancelButton)
okBtn = view.findViewById(R.id.okButton)
suggestedChips = view.findViewById(R.id.chapters)
suggestedChapters = view.findViewById(R.id.suggested_cuts)
initCutSection()
//cut list section
cutListSection = view.findViewById(R.id.list_section)
newCutBtn = view.findViewById(R.id.new_cut)
resetBtn = view.findViewById(R.id.reset_all)
chipGroup = view.findViewById(R.id.cut_list_chip_group)
selectedCuts = if (chipGroup.childCount == 0){
mutableListOf()
}else {
chipGroup.children.forEach { c ->
if ( ! (c as Chip).text.contains(":")) c.isEnabled = false
}
chipGroup.children.map { (it as Chip).text.toString() }.toMutableList()
}
initCutListSection()
if (item.downloadSections.isBlank()) cutSection.visibility = View.VISIBLE
@ -114,26 +141,32 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
lifecycleScope.launch {
try {
val url = withContext(Dispatchers.IO){
infoUtil.getStreamingUrl(item.url)
val data = withContext(Dispatchers.IO){
infoUtil.getStreamingUrlAndChapters(item.url)
}
if (data.isEmpty()) throw Exception("No Data found!")
try{
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
chapters = Gson().fromJson(data.first().toString(), listType)
}catch (ignored: Exception) {}
data.removeFirst()
if (url.isBlank()) throw Exception("No Streaming URL found!")
val urls = url.split("\n")
if (urls.size == 2){
if (data.isEmpty()) throw Exception("No Streaming URL found!")
if (data.size == 2){
val audioSource: MediaSource =
DefaultMediaSourceFactory(requireContext())
.createMediaSource(fromUri(Uri.parse(urls[0])))
.createMediaSource(fromUri(Uri.parse(data[0])))
val videoSource: MediaSource =
DefaultMediaSourceFactory(requireContext())
.createMediaSource(fromUri(Uri.parse(urls[1])))
.createMediaSource(fromUri(Uri.parse(data[1])))
(player as ExoPlayer).setMediaSource(MergingMediaSource(videoSource, audioSource))
}else{
player.addMediaItem(fromUri(Uri.parse(urls[0])))
player.addMediaItem(fromUri(Uri.parse(data[0])))
}
progress.visibility = View.GONE
populateSuggestedChapters()
player.prepare()
player.seekTo((((rangeSlider.valueFrom.toInt() * timeSeconds) / 100) * 1000).toLong())
@ -166,23 +199,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
}
private fun initCutSection(){
if (item.downloadSections.isEmpty()){
fromTextInput.editText!!.setText("0:00")
toTextInput.editText!!.setText(item.duration)
}else{
val stamps = item.downloadSections.split("-")
fromTextInput.editText!!.setText(stamps[0])
toTextInput.editText!!.setText(stamps[1].replace(";", ""))
val startSeconds = convertStringToTimestamp(stamps[0])
val endSeconds = convertStringToTimestamp(stamps[1].replace(";", ""))
val startValue = (startSeconds.toFloat() / timeSeconds) * 100
val endValue = (endSeconds.toFloat() / timeSeconds) * 100
rangeSlider.setValues(startValue, endValue)
}
fromTextInput.editText!!.setText("0:00")
toTextInput.editText!!.setText(item.duration)
rangeSlider.addOnChangeListener { rangeSlider, value, fromUser ->
val values = rangeSlider.values
@ -270,21 +288,53 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
})
cancelBtn.setOnClickListener {
cutSection.visibility = View.GONE
cutListSection.visibility = View.VISIBLE
if (chipGroup.childCount == 0){
player.stop()
dismiss()
}else{
cutSection.visibility = View.GONE
cutListSection.visibility = View.VISIBLE
}
}
okBtn.isEnabled = false
okBtn.setOnClickListener {
val chip = createChip("${fromTextInput.editText!!.text}-${toTextInput.editText!!.text}")
listener.onChangeCut(chipGroup.children.map { c -> (c as Chip).text.toString() })
chip.performClick()
player.seekTo((((rangeSlider.valueFrom.toInt() * timeSeconds) / 100) * 1000).toLong())
player.play()
cutSection.visibility = View.GONE
cutListSection.visibility = View.VISIBLE
}
populateSuggestedChapters()
}
private fun populateSuggestedChapters(){
if (chapters.isEmpty()) suggestedChapters.visibility = View.GONE
else {
suggestedChapters.visibility = View.VISIBLE
suggestedChips.removeAllViews()
chapters.forEach {
val chip = layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
chip.text = it.title
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false
suggestedChips.addView(chip)
chip.setOnClickListener { c ->
val createdChip = createChapterChip(it, null)
createdChip.performClick()
cutSection.visibility = View.GONE
cutListSection.visibility = View.VISIBLE
}
//replace existing chip to enable click events
if (selectedCuts.contains(it.title)){
val idx = selectedCuts.indexOf(it.title)
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text == it.title }
chipGroup.removeView(chipForDeletion)
createChapterChip(it, idx)
}
}
}
}
private fun initCutListSection() {
@ -295,32 +345,44 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
player.seekTo(0)
}
resetBtn.setOnClickListener {
chipGroup.removeAllViews()
listener.onChangeCut(emptyList())
player.stop()
dismiss()
}
if (item.downloadSections.isNotBlank()){
chipGroup.removeAllViews()
item.downloadSections.split(";").forEachIndexed { index, it ->
if (it.isBlank()) return
val startingValue = ((convertStringToTimestamp(it.split("-")[0]).toFloat() / timeSeconds) * 100).toInt()
val endingValue = ((convertStringToTimestamp(it.split("-")[1].replace(";", "")).toFloat() / timeSeconds) * 100).toInt()
createChip(it.replace(";", ""))
if (index == 0) rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
if (it.contains(":")) createChip(it.replace(";", ""))
else createChapterChip(ChapterItem(0, 0, it), null)
}
}
}
private fun createChip(timestamp: String) : Chip {
val startingValue = ((convertStringToTimestamp(timestamp.split("-")[0]).toFloat() / timeSeconds) * 100).toInt()
val endingValue = ((convertStringToTimestamp(timestamp.split("-")[1].replace(";", "")).toFloat() / timeSeconds) * 100).toInt()
val startTimestamp = convertStringToTimestamp(timestamp.split("-")[0].replace(";", ""))
val endTimestamp = convertStringToTimestamp(timestamp.split("-")[1].replace(";", ""))
val startingValue = ((startTimestamp.toFloat() / timeSeconds) * 100).toInt()
val endingValue = ((endTimestamp.toFloat() / timeSeconds) * 100).toInt()
val chip = layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
chip.text = timestamp
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false
chipGroup.addView(chip)
listener.onChangeCut(chipGroup.children.map { c -> (c as Chip).text.toString() })
selectedCuts.add(chip.text.toString())
listener.onChangeCut(selectedCuts)
chip.setOnClickListener {
if (chip.isChecked) {
rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
player.prepare()
player.seekTo((((startingValue * timeSeconds) / 100) * 1000).toLong())
player.play()
}else {
player.seekTo(0)
player.pause()
@ -335,7 +397,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
player.seekTo(0)
player.pause()
chipGroup.removeView(chip)
listener.onChangeCut(chipGroup.children.map { c -> (c as Chip).text.toString() })
selectedCuts.remove(chip.text.toString())
listener.onChangeCut(selectedCuts)
}
deleteDialog.show()
true
@ -344,6 +407,60 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
return chip
}
private fun createChapterChip(chapter: ChapterItem, position: Int?) : Chip {
val chip = layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
chip.text = chapter.title
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false
if (position != null) chipGroup.addView(chip, position)
else chipGroup.addView(chip)
if (! selectedCuts.contains(chapter.title))
selectedCuts.add(chip.text.toString())
listener.onChangeCut(selectedCuts)
if (chapter.start_time == 0L && chapter.end_time == 0L) {
chip.isEnabled = false
}else{
val startTimestamp = chapter.start_time.toInt()
val endTimestamp = chapter.end_time.toInt()
val startingValue = ((startTimestamp.toFloat() / timeSeconds) * 100).toInt()
val endingValue = ((endTimestamp.toFloat() / timeSeconds) * 100).toInt()
chip.setOnClickListener {
if (chip.isChecked) {
rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
player.prepare()
player.seekTo((((startingValue * timeSeconds) / 100) * 1000).toLong())
player.play()
}else {
player.seekTo(0)
player.pause()
}
}
chip.setOnLongClickListener {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + chip.text + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
player.seekTo(0)
player.pause()
chipGroup.removeView(chip)
selectedCuts.remove(chip.text.toString())
listener.onChangeCut(selectedCuts)
}
deleteDialog.show()
true
}
}
return chip
}
private fun videoProgress(player: Player?) = flow {
while (true) {
emit((player!!.currentPosition / 1000).toInt())
@ -386,5 +503,5 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
}
interface VideoCutListener{
fun onChangeCut(list: Sequence<String>)
fun onChangeCut(list: List<String>)
}

@ -1,9 +1,7 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.app.Activity
import android.content.ClipboardManager
import android.content.DialogInterface
import android.content.Intent
import android.content.*
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@ -11,10 +9,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.TextView
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
@ -249,10 +244,10 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
val cut = view.findViewById<Chip>(R.id.cut)
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
val cutVideoListener = object : VideoCutListener {
override fun onChangeCut(list: Sequence<String>) {
if (list.count() == 0){
override fun onChangeCut(list: List<String>) {
if (list.isEmpty()){
downloadItem.downloadSections = ""
cut.text = ""
cut.text = getString(R.string.cut)
splitByChapters.isEnabled = true
splitByChapters.isChecked = downloadItem.audioPreferences.splitByChapters
@ -274,13 +269,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
}
val copyURL = view.findViewById<Chip>(R.id.copy_url)
copyURL.setOnClickListener {
val clipboard: ClipboardManager =
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(downloadItem.url)
}
}catch (e : Exception){
e.printStackTrace()
}

@ -83,14 +83,14 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
}
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, null), DownloadVideoFragment(resultItem, null))
var commandTemplateNr = 0
lifecycleScope.launch{
withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber()
if(nr > 0){
commandTemplateNr = commandTemplateDao.getTotalNumber()
if(commandTemplateNr > 0){
fragments.add(DownloadCommandFragment(resultItem, null))
}else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
}
}
@ -128,7 +128,12 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
viewPager2.setCurrentItem(tab!!.position, false)
if (tab!!.position == 2 && commandTemplateNr == 0){
tabLayout.selectTab(tabLayout.getTabAt(1))
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
viewPager2.setCurrentItem(tab.position, false)
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
@ -168,6 +173,16 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
}
dismiss()
}
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.text = resultItem.url
link.setOnClickListener{
uiUtil.openLinkIntent(requireContext(), resultItem.url, null)
}
link.setOnLongClickListener{
uiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
true
}
}
private fun getDownloadItem() : DownloadItem{

@ -192,13 +192,6 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
}
}
val copyURL = view.findViewById<Chip>(R.id.copy_url)
copyURL.setOnClickListener {
val clipboard: ClipboardManager =
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(downloadItem.url)
}
} catch (e: Exception) {
e.printStackTrace()
}

@ -10,10 +10,7 @@ import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.TextView
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
@ -269,8 +266,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
val cutVideoListener = object : VideoCutListener {
override fun onChangeCut(list: Sequence<String>) {
if (list.count() == 0){
override fun onChangeCut(list: List<String>) {
if (list.isEmpty()){
downloadItem.downloadSections = ""
cut.text = getString(R.string.cut)
@ -302,13 +299,6 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
}
val copyURL = view.findViewById<Chip>(R.id.copy_url)
copyURL.setOnClickListener {
val clipboard: ClipboardManager =
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(downloadItem.url)
}
} catch (e: Exception) {
e.printStackTrace()
}

@ -3,8 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Dialog
import android.content.DialogInterface
import android.content.SharedPreferences
import android.content.*
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
@ -109,13 +108,21 @@ class FormatSelectionBottomSheetDialog(private val item: DownloadItem, private v
linearLayout.removeAllViews()
Log.e("aa", formats.toString())
for (i in formats.lastIndex downTo 0){
val it = formats[i]
val format = formats[i]
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
uiUtil.populateFormatCard(formatItem as ConstraintLayout, it)
uiUtil.populateFormatCard(formatItem as ConstraintLayout, format)
formatItem.setOnClickListener{_ ->
listener.onFormatClick(formats, it)
listener.onFormatClick(formats, format)
dismiss()
}
formatItem.setOnLongClickListener {
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("format_id", format.format_id)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, requireContext().getString(R.string.formatid_copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
true
}
linearLayout.addView(formatItem)
}
}

@ -629,10 +629,11 @@ class InfoUtil(context: Context) {
return format
}
fun getStreamingUrl(url: String) : String {
fun getStreamingUrlAndChapters(url: String) : MutableList<String?> {
try {
val request = YoutubeDLRequest(url)
request.addOption("-j")
request.addOption("--get-url")
request.addOption("--print", "%(chapters)s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
@ -643,10 +644,9 @@ class InfoUtil(context: Context) {
} catch (e: Exception) {
arrayOf(youtubeDLResponse.out)
}
val jsonObject = JSONObject(results[0]!!)
return jsonObject.getString("urls")
return results.toMutableList()
} catch (e: Exception) {
return ""
return mutableListOf()
}
}

@ -118,9 +118,13 @@ class DownloadWorker(
if (downloadItem.downloadSections.isNotBlank()){
downloadItem.downloadSections.split(";").forEach {
if (it.isBlank()) return@forEach
request.addOption("--download-sections", "*$it")
if (it.contains(":"))
request.addOption("--download-sections", "*$it")
else
request.addOption("--download-sections", it)
}
downloadItem.customFileNameTemplate += "%(autonumber)s"
downloadItem.customFileNameTemplate += " %(section_title)s %(autonumber)s"
request.addOption("--output-na-placeholder", " ")
}
}
@ -130,9 +134,8 @@ class DownloadWorker(
when(type){
DownloadViewModel.Type.audio -> {
request.addOption("-x")
var audioQualityId : String = downloadItem.format.format_id
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality)) audioQualityId = ""
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality)) audioQualityId = "bestaudio"
else if (audioQualityId == context.getString(R.string.worst_quality)) audioQualityId = "worstaudio"
if (audioQualityId.isNotBlank()){
@ -141,18 +144,33 @@ class DownloadWorker(
val ext = downloadItem.format.container
if(ext != context.getString(R.string.defaultValue) && ext != "webm"){
request.addOption("--audio-format", ext)
val codec = when(downloadItem.format.container){
"m4a" -> "aac"
"aac" -> "aac"
"mp3" -> "libmp3lame"
"flac" -> "flac"
"opus" -> "libopus"
else -> ""
}
Log.e("aa", codec)
if (codec.isEmpty()){
request.addOption("-x")
}else{
request.addOption("--remux-video", ext)
request.addOption("--ppa", "VideoRemuxer:-c:a $codec")
}
}
request.addOption("--embed-metadata")
if (downloadItem.audioPreferences.embedThumb) {
request.addOption("--embed-thumbnail")
request.addOption("--convert-thumbnails", "png")
request.addOption("--convert-thumbnails", "jpg")
try {
val config = File(context.cacheDir.absolutePath + "/downloads/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
val configData =
"--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
config.writeText(configData)
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
request.addOption("--config", config.absolutePath)
} catch (ignored: Exception) {}
}

@ -24,7 +24,7 @@
android:checkable="true"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_margin="10dp">
android:layout_margin="20dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view"

@ -66,7 +66,6 @@
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/download_viewpager"
android:layout_width="match_parent"
android:paddingHorizontal="10dp"
android:layout_height="match_parent" />
</LinearLayout>
<include layout="@layout/history_no_results"

@ -87,7 +87,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:singleLine="true"
android:text="@string/app_name"
android:textSize="15sp"
app:icon="@drawable/ic_link" />
</LinearLayout>

@ -69,7 +69,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/suggested_cuts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent"
@ -119,6 +119,43 @@
</LinearLayout>
<LinearLayout
android:id="@+id/suggested_cuts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingTop="10dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout5"
app:layout_constraintVertical_bias="0.0"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/suggested" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chapters"
android:layout_width="wrap_content"
app:chipSpacingVertical="-10dp"
app:chipSpacing="0dp"
android:layout_height="wrap_content"
app:singleLine="true" />
</HorizontalScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -127,7 +164,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="20dp"
app:layout_constraintTop_toBottomOf="@+id/linearLayout5"
app:layout_constraintTop_toBottomOf="@+id/suggested_cuts"
app:layout_constraintVertical_bias="1.0"
>
@ -180,6 +217,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/reset_all"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_baseline_delete_outline_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/new_cut"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/new_cut"

@ -105,6 +105,18 @@
android:id="@+id/download_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:singleLine="true"
android:text="@string/app_name"
android:textSize="15sp"
app:icon="@drawable/ic_link" />
</LinearLayout>

@ -11,7 +11,8 @@
android:layout_width="0dp"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
android:layout_height="wrap_content"
android:padding="10dp"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:checkable="true"
android:clickable="true"
android:focusable="true"

@ -5,6 +5,7 @@
android:clickable="true"
android:layout_height="wrap_content"
android:paddingVertical="5dp"
android:paddingHorizontal="10dp"
android:background="?android:attr/selectableItemBackground"
xmlns:android="http://schemas.android.com/apk/res/android"
android:focusable="true">

@ -59,7 +59,6 @@
android:layout_width="match_parent"
android:scrollbars="none"
android:paddingTop="10dp"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content">
<LinearLayout

@ -12,7 +12,6 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
@ -20,7 +19,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:paddingBottom="10dp"
android:padding="10dp"
android:hint="@string/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
@ -35,7 +34,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:padding="10dp"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
@ -80,13 +79,16 @@
android:id="@+id/audio_quality_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:text="@string/audio_quality" />
<include layout="@layout/format_item" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/outputPath"
android:paddingHorizontal="10dp"
android:paddingTop="10dp"
android:paddingBottom="0dp"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -102,8 +104,7 @@
<TextView
android:id="@+id/freespace"
android:paddingHorizontal="10dp"
android:paddingVertical="5dp"
android:paddingHorizontal="20dp"
android:textSize="13sp"
android:textColor="@android:color/tab_indicator_text"
android:layout_width="wrap_content"
@ -112,7 +113,7 @@
<LinearLayout
android:id="@+id/adjust_audio"
android:layout_width="match_parent"
android:paddingVertical="10dp"
android:padding="10dp"
android:layout_height="wrap_content"
android:orientation="vertical"
>
@ -205,22 +206,6 @@
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<com.google.android.material.chip.Chip
android:id="@+id/copy_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/copy_url"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_clipboard"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>

@ -111,21 +111,6 @@
android:text="@string/edit_selected"
app:chipIcon="@drawable/ic_edit" />
<com.google.android.material.chip.Chip
android:id="@+id/copy_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/copy_url"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_clipboard"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup>

@ -9,7 +9,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -20,7 +19,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/title"
android:paddingBottom="10dp">
android:padding="10dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
@ -33,7 +32,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="10dp">
android:padding="10dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/author_textinput"
@ -74,6 +73,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:text="@string/video_quality" />
<include layout="@layout/format_item" />
@ -85,7 +85,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/save_dir"
android:paddingTop="10dp">
android:paddingHorizontal="10dp"
android:paddingTop="10dp"
android:paddingBottom="0dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
@ -97,8 +99,7 @@
<TextView
android:id="@+id/freespace"
android:paddingHorizontal="10dp"
android:paddingVertical="5dp"
android:paddingHorizontal="20dp"
android:textSize="13sp"
android:textColor="@android:color/tab_indicator_text"
android:layout_width="wrap_content"
@ -110,7 +111,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingVertical="10dp">
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
@ -213,24 +214,6 @@
</HorizontalScrollView>
<com.google.android.material.chip.Chip
android:id="@+id/copy_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/copy_url"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_clipboard"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>

@ -36,6 +36,7 @@
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"

@ -57,7 +57,8 @@
<com.google.android.material.chip.ChipGroup
android:id="@+id/shortcutsChipGroup"
android:layout_width="wrap_content"
app:chipSpacingVertical="0dp"
app:chipSpacingVertical="-10dp"
app:chipSpacing="0dp"
android:layout_height="wrap_content"
app:singleLine="false">

@ -192,4 +192,7 @@
<string name="ignore_battery_optimization">Ignore Battery Optimization</string>
<string name="cut">Cut</string>
<string name="new_cut">New Cut</string>
<string name="add_template_first">You need to have at least one command template created!</string>
<string name="formatid_copied_to_clipboard">Format ID copied to clipboard</string>
<string name="suggested">Suggested</string>
</resources>

@ -1,4 +1,4 @@
#Sun Mar 05 19:03:41 CET 2023
#Sat Apr 08 15:57:50 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists

Loading…
Cancel
Save