more stuff
Quick download item log has no title now, maybe you can use "null" or the link? 'Cause it looks hilarious, LOL Fix search history not showing sometimes Add download type in logs fix video not showing in cut screen fixed xhamster not showing results
This commit is contained in:
parent
366af9270e
commit
f303a4f879
32 changed files with 1265 additions and 439 deletions
|
|
@ -192,8 +192,18 @@ dependencies {
|
||||||
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
|
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||||
implementation "com.google.android.exoplayer:exoplayer:2.18.7"
|
|
||||||
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
|
||||||
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
||||||
|
|
||||||
|
// For media playback using ExoPlayer
|
||||||
|
implementation("androidx.media3:media3-exoplayer:$media3_version")
|
||||||
|
// For DASH playback support with ExoPlayer
|
||||||
|
implementation("androidx.media3:media3-exoplayer-dash:$media3_version")
|
||||||
|
// For HLS playback support with ExoPlayer
|
||||||
|
implementation("androidx.media3:media3-exoplayer-hls:$media3_version")
|
||||||
|
implementation("androidx.media3:media3-ui:$media3_version")
|
||||||
|
// For RTSP playback support with ExoPlayer
|
||||||
|
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
|
||||||
|
implementation("androidx.media3:media3-datasource-cronet:$media3_version")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 8,
|
"version": 8,
|
||||||
"identityHash": "b21f2aa737d29eef2c1bf6418b314726",
|
"identityHash": "3d472df655311f8e531602dca2886c34",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "results",
|
"tableName": "results",
|
||||||
|
|
@ -180,7 +180,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "downloads",
|
"tableName": "downloads",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER)",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER)",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
|
@ -286,6 +286,13 @@
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "extraCommands",
|
||||||
|
"columnName": "extraCommands",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "customFileNameTemplate",
|
"fieldPath": "customFileNameTemplate",
|
||||||
"columnName": "customFileNameTemplate",
|
"columnName": "customFileNameTemplate",
|
||||||
|
|
@ -446,7 +453,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "logs",
|
"tableName": "logs",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL)",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
|
@ -465,6 +472,24 @@
|
||||||
"columnName": "content",
|
"columnName": "content",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "format",
|
||||||
|
"columnName": "format",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadType",
|
||||||
|
"columnName": "downloadType",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadTime",
|
||||||
|
"columnName": "downloadTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
|
@ -480,7 +505,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b21f2aa737d29eef2c1bf6418b314726')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3d472df655311f8e531602dca2886c34')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,16 +16,20 @@ import android.os.Handler
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.FragmentContainerView
|
import androidx.fragment.app.FragmentContainerView
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.deniscerri.ytdlnis.adapter
|
package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.text.format.DateFormat
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -12,29 +14,38 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<LogItem?, DownloadLogsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<LogItem?, DownloadLogsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
|
private val checkedItems: ArrayList<Long>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
checkedItems = ArrayList()
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
val item: ConstraintLayout
|
val item: MaterialCardView
|
||||||
|
|
||||||
init {
|
init {
|
||||||
item = itemView.findViewById(R.id.download_log_item_constraint)
|
item = itemView.findViewById(R.id.log_card_view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val cardView = LayoutInflater.from(parent.context)
|
val cardView = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.download_log_item, parent, false)
|
.inflate(R.layout.log_card, parent, false)
|
||||||
return ViewHolder(cardView, onItemClickListener)
|
return ViewHolder(cardView, onItemClickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,20 +56,112 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac
|
||||||
val title = card.findViewById<TextView>(R.id.title)
|
val title = card.findViewById<TextView>(R.id.title)
|
||||||
title.text = item?.title
|
title.text = item?.title
|
||||||
|
|
||||||
val delete = card.findViewById<Button>(R.id.delete)
|
val downloadedTime = card.findViewById<TextView>(R.id.downloadedTime)
|
||||||
|
downloadedTime.text = SimpleDateFormat(
|
||||||
|
DateFormat.getBestDateTimePattern(
|
||||||
|
Locale.getDefault(), "ddMMMyyyy - HH:mm"), Locale.getDefault()).format(item!!.downloadTime)
|
||||||
|
|
||||||
delete.setOnClickListener {
|
val downloadTypeIcon = card.findViewById<MaterialButton>(R.id.download_type)
|
||||||
onItemClickListener.onDeleteClick(item!!)
|
when(item.downloadType){
|
||||||
|
DownloadViewModel.Type.audio -> downloadTypeIcon.setIconResource(R.drawable.ic_music)
|
||||||
|
DownloadViewModel.Type.video -> downloadTypeIcon.setIconResource(R.drawable.ic_video)
|
||||||
|
DownloadViewModel.Type.command -> downloadTypeIcon.setIconResource(R.drawable.ic_terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
title.setOnClickListener {
|
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||||
onItemClickListener.onItemClick(item!!)
|
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||||
|
View.GONE
|
||||||
|
else formatNote!!.text = item.format.format_note.uppercase()
|
||||||
|
|
||||||
|
val codec = card.findViewById<TextView>(R.id.codec)
|
||||||
|
val codecText =
|
||||||
|
if (item.format.encoding != "") {
|
||||||
|
item.format.encoding.uppercase()
|
||||||
|
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||||
|
item.format.vcodec.uppercase()
|
||||||
|
} else {
|
||||||
|
item.format.acodec.uppercase()
|
||||||
|
}
|
||||||
|
if (codecText == "" || codecText == "none"){
|
||||||
|
codec.visibility = View.GONE
|
||||||
|
}else{
|
||||||
|
codec.visibility = View.VISIBLE
|
||||||
|
codec.text = codecText
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileSize = card.findViewById<TextView>(R.id.file_size)
|
||||||
|
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||||
|
if (fileSizeReadable == "?") fileSize.visibility = View.GONE
|
||||||
|
else fileSize.text = fileSizeReadable
|
||||||
|
|
||||||
|
if (checkedItems.contains(item.id)) {
|
||||||
|
card.isChecked = true
|
||||||
|
card.strokeWidth = 5
|
||||||
|
} else {
|
||||||
|
card.isChecked = false
|
||||||
|
card.strokeWidth = 0
|
||||||
|
}
|
||||||
|
card.setOnClickListener {
|
||||||
|
if (checkedItems.size > 0) {
|
||||||
|
checkCard(card, item.id)
|
||||||
|
} else {
|
||||||
|
onItemClickListener.onItemClick(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
card.setOnLongClickListener {
|
||||||
|
checkCard(card, item.id)
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun clearCheckeditems() {
|
||||||
|
for (i in 0 until itemCount){
|
||||||
|
val item = getItem(i)
|
||||||
|
if (checkedItems.find { it == item?.id } != null){
|
||||||
|
checkedItems.remove(item?.id)
|
||||||
|
notifyItemChanged(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkedItems.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun checkAll(items: List<LogItem?>?){
|
||||||
|
checkedItems.clear()
|
||||||
|
checkedItems.addAll(items!!.map { it!!.id })
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun invertSelected(items: List<LogItem?>?){
|
||||||
|
val invertedList = mutableListOf<Long>()
|
||||||
|
items?.forEach {
|
||||||
|
if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
|
||||||
|
}
|
||||||
|
checkedItems.clear()
|
||||||
|
checkedItems.addAll(invertedList)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCard(card: MaterialCardView, itemID: Long) {
|
||||||
|
if (card.isChecked) {
|
||||||
|
card.strokeWidth = 0
|
||||||
|
checkedItems.remove(itemID)
|
||||||
|
} else {
|
||||||
|
card.strokeWidth = 5
|
||||||
|
checkedItems.add(itemID)
|
||||||
|
}
|
||||||
|
card.isChecked = !card.isChecked
|
||||||
|
onItemClickListener.onCardSelect(itemID, card.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
interface OnItemClickListener {
|
interface OnItemClickListener {
|
||||||
fun onItemClick(item: LogItem)
|
fun onItemClick(itemID: Long)
|
||||||
fun onDeleteClick(item: LogItem)
|
fun onDeleteClick(item: LogItem)
|
||||||
|
fun onCardSelect(itemID: Long, isChecked: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ data class DownloadItem(
|
||||||
val playlistTitle: String,
|
val playlistTitle: String,
|
||||||
val audioPreferences : AudioPreferences,
|
val audioPreferences : AudioPreferences,
|
||||||
val videoPreferences: VideoPreferences,
|
val videoPreferences: VideoPreferences,
|
||||||
|
@ColumnInfo(defaultValue = "")
|
||||||
|
var extraCommands: String,
|
||||||
var customFileNameTemplate: String,
|
var customFileNameTemplate: String,
|
||||||
var SaveThumb: Boolean,
|
var SaveThumb: Boolean,
|
||||||
@ColumnInfo(defaultValue = "Queued")
|
@ColumnInfo(defaultValue = "Queued")
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
package com.deniscerri.ytdlnis.database.models
|
package com.deniscerri.ytdlnis.database.models
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
|
||||||
@Entity(tableName = "logs")
|
@Entity(tableName = "logs")
|
||||||
data class LogItem(
|
data class LogItem(
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long,
|
var id: Long,
|
||||||
var title: String,
|
var title: String,
|
||||||
var content: String
|
var content: String,
|
||||||
|
var format: Format,
|
||||||
|
var downloadType: DownloadViewModel.Type,
|
||||||
|
var downloadTime: Long,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
sharedPreferences.getString("video_format", "Default")!!,
|
sharedPreferences.getString("video_format", "Default")!!,
|
||||||
"",
|
"",
|
||||||
resultItem.formats,
|
resultItem.formats,
|
||||||
downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null
|
downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +302,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
container,
|
container,
|
||||||
"",
|
"",
|
||||||
ArrayList(),
|
ArrayList(),
|
||||||
path, historyItem.website, "", "", audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null
|
path, historyItem.website, "", "", audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
arguments?.remove("url")
|
arguments?.remove("url")
|
||||||
}
|
}
|
||||||
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
|
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
|
||||||
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
|
lifecycleScope.launch {
|
||||||
|
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,7 +293,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}else{
|
}else{
|
||||||
linkYouCopied!!.visibility = GONE
|
linkYouCopied!!.visibility = GONE
|
||||||
}
|
}
|
||||||
updateSearchViewItems(searchView!!.editText.text, linkYouCopied)
|
lifecycleScope.launch {
|
||||||
|
updateSearchViewItems(searchView!!.editText.text, linkYouCopied)
|
||||||
|
}
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
linkYouCopied!!.visibility = GONE
|
linkYouCopied!!.visibility = GONE
|
||||||
|
|
@ -301,7 +305,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
|
|
||||||
searchView!!.editText.doAfterTextChanged {
|
searchView!!.editText.doAfterTextChanged {
|
||||||
if (searchView!!.currentTransitionState != SearchView.TransitionState.SHOWN) return@doAfterTextChanged
|
if (searchView!!.currentTransitionState != SearchView.TransitionState.SHOWN) return@doAfterTextChanged
|
||||||
updateSearchViewItems(it, linkYouCopied)
|
lifecycleScope.launch {
|
||||||
|
updateSearchViewItems(it, linkYouCopied)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchView!!.editText.setOnTouchListener(OnTouchListener { _, event ->
|
searchView!!.editText.setOnTouchListener(OnTouchListener { _, event ->
|
||||||
|
|
@ -368,91 +374,89 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
private fun updateSearchViewItems(it: Editable?, linkYouCopied: View?){
|
private suspend fun updateSearchViewItems(it: Editable?, linkYouCopied: View?){
|
||||||
lifecycleScope.launch {
|
searchSuggestionsLinearLayout!!.removeAllViews()
|
||||||
searchSuggestionsLinearLayout!!.removeAllViews()
|
searchHistoryLinearLayout!!.removeAllViews()
|
||||||
searchHistoryLinearLayout!!.removeAllViews()
|
|
||||||
|
|
||||||
searchSuggestionsLinearLayout!!.visibility = GONE
|
searchSuggestionsLinearLayout!!.visibility = GONE
|
||||||
searchHistoryLinearLayout!!.visibility = GONE
|
searchHistoryLinearLayout!!.visibility = GONE
|
||||||
linkYouCopied!!.visibility = GONE
|
linkYouCopied!!.visibility = GONE
|
||||||
|
|
||||||
if (searchView!!.editText.text.isEmpty()){
|
if (searchView!!.editText.text.isEmpty()){
|
||||||
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||||
|
}else{
|
||||||
|
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())
|
||||||
}else{
|
}else{
|
||||||
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
|
emptyList()
|
||||||
}
|
|
||||||
val suggestions = withContext(Dispatchers.IO){
|
|
||||||
if (it!!.isEmpty()) {
|
|
||||||
resultViewModel.getSearchHistory().map { it.query }
|
|
||||||
}else if (sharedPreferences!!.getBoolean("search_suggestions", false)){
|
|
||||||
infoUtil!!.getSearchSuggestions(it.toString())
|
|
||||||
}else{
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (it!!.isEmpty()){
|
if (it!!.isEmpty()){
|
||||||
for (i in suggestions.indices) {
|
for (i in suggestions.indices) {
|
||||||
val v = LayoutInflater.from(fragmentContext)
|
val v = LayoutInflater.from(fragmentContext)
|
||||||
.inflate(R.layout.search_suggestion_item, null)
|
.inflate(R.layout.search_suggestion_item, null)
|
||||||
val textView = v.findViewById<TextView>(R.id.suggestion_text)
|
val textView = v.findViewById<TextView>(R.id.suggestion_text)
|
||||||
textView.text = suggestions[i]
|
textView.text = suggestions[i]
|
||||||
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
|
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
searchHistoryLinearLayout!!.addView(
|
searchHistoryLinearLayout!!.addView(
|
||||||
v
|
v
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
textView.setOnClickListener {
|
textView.setOnClickListener {
|
||||||
searchView!!.setText(textView.text)
|
searchView!!.setText(textView.text)
|
||||||
initSearch(searchView!!)
|
initSearch(searchView!!)
|
||||||
}
|
}
|
||||||
textView.setOnLongClickListener {
|
textView.setOnLongClickListener {
|
||||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + textView.text + "\"!")
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + textView.text + "\"!")
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
searchHistoryLinearLayout!!.removeView(v)
|
searchHistoryLinearLayout!!.removeView(v)
|
||||||
resultViewModel.removeSearchQueryFromHistory(textView.text.toString())
|
resultViewModel.removeSearchQueryFromHistory(textView.text.toString())
|
||||||
}
|
|
||||||
deleteDialog.show()
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
|
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
|
||||||
mb.setOnClickListener {
|
mb.setOnClickListener {
|
||||||
searchView!!.setText(textView.text)
|
searchView!!.setText(textView.text)
|
||||||
searchView!!.editText.setSelection(searchView!!.editText.length())
|
searchView!!.editText.setSelection(searchView!!.editText.length())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
searchHistoryLinearLayout!!.visibility = VISIBLE
|
|
||||||
if (linkYouCopied.findViewById<TextView>(R.id.suggestion_text).text.isNotEmpty()){
|
|
||||||
linkYouCopied.visibility = VISIBLE
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
for (i in suggestions.indices) {
|
|
||||||
val v = LayoutInflater.from(fragmentContext)
|
|
||||||
.inflate(R.layout.search_suggestion_item, null)
|
|
||||||
val textView = v.findViewById<TextView>(R.id.suggestion_text)
|
|
||||||
textView.text = suggestions[i]
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
|
||||||
searchSuggestionsLinearLayout!!.addView(
|
|
||||||
v
|
|
||||||
)
|
|
||||||
}
|
|
||||||
textView.setOnClickListener {
|
|
||||||
searchView!!.setText(textView.text)
|
|
||||||
initSearch(searchView!!)
|
|
||||||
}
|
|
||||||
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
|
|
||||||
mb.setOnClickListener {
|
|
||||||
searchView!!.setText(textView.text)
|
|
||||||
searchView!!.editText.setSelection(searchView!!.editText.length())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
searchSuggestionsLinearLayout!!.visibility = VISIBLE
|
|
||||||
}
|
}
|
||||||
|
searchHistoryLinearLayout!!.visibility = VISIBLE
|
||||||
|
if (linkYouCopied.findViewById<TextView>(R.id.suggestion_text).text.isNotEmpty()){
|
||||||
|
linkYouCopied.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
for (i in suggestions.indices) {
|
||||||
|
val v = LayoutInflater.from(fragmentContext)
|
||||||
|
.inflate(R.layout.search_suggestion_item, null)
|
||||||
|
val textView = v.findViewById<TextView>(R.id.suggestion_text)
|
||||||
|
textView.text = suggestions[i]
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
searchSuggestionsLinearLayout!!.addView(
|
||||||
|
v
|
||||||
|
)
|
||||||
|
}
|
||||||
|
textView.setOnClickListener {
|
||||||
|
searchView!!.setText(textView.text)
|
||||||
|
initSearch(searchView!!)
|
||||||
|
}
|
||||||
|
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
|
||||||
|
mb.setOnClickListener {
|
||||||
|
searchView!!.setText(textView.text)
|
||||||
|
searchView!!.editText.setSelection(searchView!!.editText.length())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchSuggestionsLinearLayout!!.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,26 +15,23 @@ import android.widget.*
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.media3.common.AudioAttributes
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.MediaItem.fromUri
|
||||||
|
import androidx.media3.datasource.DefaultDataSource
|
||||||
|
import androidx.media3.datasource.cronet.CronetDataSource
|
||||||
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||||
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
|
import androidx.media3.ui.PlayerView
|
||||||
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.google.android.exoplayer2.C
|
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl
|
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory
|
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
|
||||||
import com.google.android.exoplayer2.MediaItem.fromUri
|
|
||||||
import com.google.android.exoplayer2.Player
|
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo
|
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
|
|
||||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource
|
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
|
||||||
import com.google.android.exoplayer2.util.MimeTypes
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
|
@ -53,15 +50,17 @@ import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.chromium.net.CronetEngine
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
|
||||||
class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls : String?, private var chapters: List<ChapterItem>?, private val listener: VideoCutListener) : BottomSheetDialogFragment() {
|
class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls : String?, private var chapters: List<ChapterItem>?, private val listener: VideoCutListener) : BottomSheetDialogFragment() {
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var behavior: BottomSheetBehavior<View>
|
||||||
private lateinit var infoUtil: InfoUtil
|
private lateinit var infoUtil: InfoUtil
|
||||||
private lateinit var player: Player
|
private lateinit var player: ExoPlayer
|
||||||
|
|
||||||
private lateinit var cutSection : ConstraintLayout
|
private lateinit var cutSection : ConstraintLayout
|
||||||
private lateinit var durationText: TextView
|
private lateinit var durationText: TextView
|
||||||
|
|
@ -92,6 +91,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi", "SetTextI18n")
|
@SuppressLint("RestrictedApi", "SetTextI18n")
|
||||||
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
override fun setupDialog(dialog: Dialog, style: Int) {
|
override fun setupDialog(dialog: Dialog, style: Int) {
|
||||||
super.setupDialog(dialog, style)
|
super.setupDialog(dialog, style)
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.cut_video_sheet, null)
|
val view = LayoutInflater.from(context).inflate(R.layout.cut_video_sheet, null)
|
||||||
|
|
@ -104,30 +104,53 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
behavior.peekHeight = displayMetrics.heightPixels
|
behavior.peekHeight = displayMetrics.heightPixels
|
||||||
}
|
}
|
||||||
|
|
||||||
val renderersFactory = DefaultRenderersFactory(requireContext().applicationContext)
|
// val renderersFactory = DefaultRenderersFactory(requireContext().applicationContext)
|
||||||
.setEnableDecoderFallback(true)
|
// .setEnableDecoderFallback(true)
|
||||||
.setExtensionRendererMode(EXTENSION_RENDERER_MODE_PREFER)
|
// .setExtensionRendererMode(EXTENSION_RENDERER_MODE_PREFER)
|
||||||
.setMediaCodecSelector { mimeType, requiresSecureDecoder, requiresTunnelingDecoder ->
|
// .setMediaCodecSelector { mimeType, requiresSecureDecoder, requiresTunnelingDecoder ->
|
||||||
var decoderInfo: MutableList<MediaCodecInfo> =
|
// var decoderInfo: MutableList<MediaCodecInfo> =
|
||||||
MediaCodecSelector.DEFAULT
|
// MediaCodecSelector.DEFAULT
|
||||||
.getDecoderInfos(
|
// .getDecoderInfos(
|
||||||
mimeType,
|
// mimeType,
|
||||||
requiresSecureDecoder,
|
// requiresSecureDecoder,
|
||||||
requiresTunnelingDecoder
|
// requiresTunnelingDecoder
|
||||||
)
|
// )
|
||||||
if (MimeTypes.VIDEO_H264 == mimeType) {
|
// if (MimeTypes.VIDEO_H264 == mimeType) {
|
||||||
// copy the list because MediaCodecSelector.DEFAULT returns an unmodifiable list
|
// // copy the list because MediaCodecSelector.DEFAULT returns an unmodifiable list
|
||||||
decoderInfo = ArrayList(decoderInfo)
|
// decoderInfo = ArrayList(decoderInfo)
|
||||||
decoderInfo.reverse()
|
// decoderInfo.reverse()
|
||||||
}
|
// }
|
||||||
decoderInfo
|
// decoderInfo
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
val cronetEngine: CronetEngine = CronetEngine.Builder(App.instance)
|
||||||
|
.enableHttp2(true)
|
||||||
|
.enableQuic(true)
|
||||||
|
.enableBrotli(true)
|
||||||
|
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 1024L * 1024L) // 1MiB
|
||||||
|
.build()
|
||||||
|
|
||||||
val trackSelector = DefaultTrackSelector(requireContext())
|
val trackSelector = DefaultTrackSelector(requireContext())
|
||||||
val loadControl = DefaultLoadControl()
|
val loadControl = DefaultLoadControl.Builder()
|
||||||
|
// cache the last three minutes
|
||||||
|
.setBackBuffer(1000 * 60 * 3, true)
|
||||||
|
.setBufferDurationsMs(
|
||||||
|
1000 * 10, // exo default is 50s
|
||||||
|
50000,
|
||||||
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||||
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val cronetDataSourceFactory = CronetDataSource.Factory(
|
||||||
|
cronetEngine,
|
||||||
|
Executors.newCachedThreadPool()
|
||||||
|
)
|
||||||
|
val dataSourceFactory = DefaultDataSource.Factory(requireContext(), cronetDataSourceFactory)
|
||||||
|
|
||||||
player = ExoPlayer.Builder(requireContext())
|
player = ExoPlayer.Builder(requireContext())
|
||||||
.setUsePlatformDiagnostics(false)
|
.setUsePlatformDiagnostics(false)
|
||||||
|
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
|
||||||
.setTrackSelector(trackSelector)
|
.setTrackSelector(trackSelector)
|
||||||
.setLoadControl(loadControl)
|
.setLoadControl(loadControl)
|
||||||
.setHandleAudioBecomingNoisy(true)
|
.setHandleAudioBecomingNoisy(true)
|
||||||
|
|
@ -139,8 +162,9 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
,
|
,
|
||||||
true)
|
true)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val frame = view.findViewById<MaterialCardView>(R.id.frame_layout)
|
val frame = view.findViewById<MaterialCardView>(R.id.frame_layout)
|
||||||
val videoView = view.findViewById<StyledPlayerView>(R.id.video_view)
|
val videoView = view.findViewById<PlayerView>(R.id.video_view)
|
||||||
videoView.player = player
|
videoView.player = player
|
||||||
timeSeconds = convertStringToTimestamp(item.duration)
|
timeSeconds = convertStringToTimestamp(item.duration)
|
||||||
if (chapters == null) chapters = emptyList()
|
if (chapters == null) chapters = emptyList()
|
||||||
|
|
@ -199,7 +223,9 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
|
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
|
||||||
chapters = Gson().fromJson(data.first().toString(), listType)
|
chapters = Gson().fromJson(data.first().toString(), listType)
|
||||||
data.removeFirst()
|
data.removeFirst()
|
||||||
}catch (ignored: Exception) {}
|
}catch (ignored: Exception) {
|
||||||
|
data.removeFirst()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.isEmpty()) throw Exception("No Streaming URL found!")
|
if (data.isEmpty()) throw Exception("No Streaming URL found!")
|
||||||
|
|
@ -210,7 +236,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
val videoSource: MediaSource =
|
val videoSource: MediaSource =
|
||||||
DefaultMediaSourceFactory(requireContext())
|
DefaultMediaSourceFactory(requireContext())
|
||||||
.createMediaSource(fromUri(Uri.parse(data[1])))
|
.createMediaSource(fromUri(Uri.parse(data[1])))
|
||||||
(player as ExoPlayer).setMediaSource(MergingMediaSource(videoSource, audioSource))
|
player.setMediaSource(MergingMediaSource(videoSource, audioSource))
|
||||||
}else{
|
}else{
|
||||||
player.addMediaItem(fromUri(Uri.parse(data[0])))
|
player.addMediaItem(fromUri(Uri.parse(data[0])))
|
||||||
}
|
}
|
||||||
|
|
@ -229,12 +255,12 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
}
|
}
|
||||||
//poll video progress
|
//poll video progress
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
videoProgress(player).collect {
|
videoProgress(player).collect { p ->
|
||||||
val currentTime = infoUtil.formatIntegerDuration(it, Locale.US)
|
val currentTime = infoUtil.formatIntegerDuration(p, Locale.US)
|
||||||
durationText.text = "$currentTime / ${item.duration}"
|
durationText.text = "$currentTime / ${item.duration}"
|
||||||
val startTimestamp = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
val startTimestamp = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
||||||
val endTimestamp = convertStringToTimestamp(toTextInput.editText!!.text.toString())
|
val endTimestamp = convertStringToTimestamp(toTextInput.editText!!.text.toString())
|
||||||
if (it >= endTimestamp){
|
if (p >= endTimestamp){
|
||||||
player.prepare()
|
player.prepare()
|
||||||
player.seekTo((startTimestamp * 1000).toLong())
|
player.seekTo((startTimestamp * 1000).toLong())
|
||||||
}
|
}
|
||||||
|
|
@ -536,7 +562,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun videoProgress(player: Player?) = flow {
|
private fun videoProgress(player: ExoPlayer?) = flow {
|
||||||
while (true) {
|
while (true) {
|
||||||
emit((player!!.currentPosition / 1000).toInt())
|
emit((player!!.currentPosition / 1000).toInt())
|
||||||
delay(1000)
|
delay(1000)
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,29 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloadcard
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -33,7 +38,10 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
@ -41,6 +49,7 @@ import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
@ -56,6 +65,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
private lateinit var author : TextInputLayout
|
private lateinit var author : TextInputLayout
|
||||||
private lateinit var saveDir : TextInputLayout
|
private lateinit var saveDir : TextInputLayout
|
||||||
private lateinit var freeSpace : TextView
|
private lateinit var freeSpace : TextView
|
||||||
|
private lateinit var infoUtil: InfoUtil
|
||||||
|
|
||||||
lateinit var downloadItem : DownloadItem
|
lateinit var downloadItem : DownloadItem
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
|
@ -68,7 +78,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
|
infoUtil = InfoUtil(requireContext())
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,6 +338,47 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
}else{
|
}else{
|
||||||
cut.isEnabled = false
|
cut.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
||||||
|
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||||
|
extraCommands.visibility = View.VISIBLE
|
||||||
|
extraCommands.setOnClickListener {
|
||||||
|
val bottomSheet = BottomSheetDialog(requireContext())
|
||||||
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
bottomSheet.setContentView(R.layout.extra_commands_bottom_sheet)
|
||||||
|
|
||||||
|
val text = bottomSheet.findViewById<EditText>(R.id.command)
|
||||||
|
val currentCommand = infoUtil.buildYoutubeDLRequest(downloadItem).buildCommand().joinToString(" ")
|
||||||
|
bottomSheet.findViewById<TextView>(R.id.currentText)?.text = currentCommand
|
||||||
|
|
||||||
|
text?.setText(downloadItem.extraCommands)
|
||||||
|
|
||||||
|
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
text!!.postDelayed({
|
||||||
|
text.setSelection(text.length())
|
||||||
|
text.requestFocus()
|
||||||
|
imm.showSoftInput(text, 0)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
val ok = bottomSheet.findViewById<Button>(R.id.okButton)
|
||||||
|
ok?.setOnClickListener {
|
||||||
|
downloadItem.extraCommands = text.text.toString()
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheet.show()
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||||
|
bottomSheet.window!!.setLayout(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
extraCommands.visibility = View.GONE
|
||||||
|
}
|
||||||
}catch (e : Exception){
|
}catch (e : Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,31 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloadcard
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.icu.text.IDNA.Info
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -33,7 +40,9 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
@ -56,6 +65,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
private lateinit var author : TextInputLayout
|
private lateinit var author : TextInputLayout
|
||||||
private lateinit var saveDir : TextInputLayout
|
private lateinit var saveDir : TextInputLayout
|
||||||
private lateinit var freeSpace : TextView
|
private lateinit var freeSpace : TextView
|
||||||
|
private lateinit var infoUtil: InfoUtil
|
||||||
|
|
||||||
lateinit var downloadItem: DownloadItem
|
lateinit var downloadItem: DownloadItem
|
||||||
|
|
||||||
|
|
@ -69,10 +79,12 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java]
|
||||||
|
infoUtil = InfoUtil(requireContext())
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
@ -388,6 +400,45 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
downloadItem.videoPreferences.removeAudio = removeAudio.isChecked
|
downloadItem.videoPreferences.removeAudio = removeAudio.isChecked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
||||||
|
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||||
|
extraCommands.visibility = View.VISIBLE
|
||||||
|
extraCommands.setOnClickListener {
|
||||||
|
val bottomSheet = BottomSheetDialog(requireContext())
|
||||||
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
bottomSheet.setContentView(R.layout.extra_commands_bottom_sheet)
|
||||||
|
|
||||||
|
val text = bottomSheet.findViewById<EditText>(R.id.command)
|
||||||
|
val currentCommand = infoUtil.buildYoutubeDLRequest(downloadItem).buildCommand().joinToString(" ")
|
||||||
|
bottomSheet.findViewById<TextView>(R.id.currentText)?.text = currentCommand
|
||||||
|
|
||||||
|
text?.setText(downloadItem.extraCommands)
|
||||||
|
|
||||||
|
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
text!!.postDelayed({
|
||||||
|
text.setSelection(text.length())
|
||||||
|
text.requestFocus()
|
||||||
|
imm.showSoftInput(text, 0)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
val ok = bottomSheet.findViewById<Button>(R.id.okButton)
|
||||||
|
ok?.setOnClickListener {
|
||||||
|
downloadItem.extraCommands = text.text.toString()
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheet.show()
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||||
|
bottomSheet.window!!.setLayout(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
extraCommands.visibility = View.GONE
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.CookieAdapter
|
import com.deniscerri.ytdlnis.adapter.CookieAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.neo.highlight.core.Highlight
|
import com.neo.highlight.core.Highlight
|
||||||
import com.neo.highlight.util.listener.HighlightTextWatcher
|
import com.neo.highlight.util.listener.HighlightTextWatcher
|
||||||
import com.neo.highlight.util.scheme.ColorScheme
|
import com.neo.highlight.util.scheme.ColorScheme
|
||||||
|
|
@ -65,6 +66,7 @@ class DownloadLogFragment : Fragment() {
|
||||||
val clipboard: ClipboardManager =
|
val clipboard: ClipboardManager =
|
||||||
mainActivity.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
mainActivity.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
clipboard.setText(content.text)
|
clipboard.setText(content.text)
|
||||||
|
Snackbar.make(content, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
val id = arguments?.getLong("logID")
|
val id = arguments?.getLong("logID")
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,24 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.downloadLogs
|
package com.deniscerri.ytdlnis.ui.more.downloadLogs
|
||||||
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
|
|
@ -20,17 +28,22 @@ import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import java.io.File
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListener {
|
class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListener {
|
||||||
private lateinit var recyclerView: RecyclerView
|
private lateinit var recyclerView: RecyclerView
|
||||||
private lateinit var downloadLogAdapter: DownloadLogsAdapter
|
private lateinit var downloadLogAdapter: DownloadLogsAdapter
|
||||||
private lateinit var noResults: RelativeLayout
|
private lateinit var noResults: RelativeLayout
|
||||||
private lateinit var fileList: MutableList<File>
|
private lateinit var items: MutableList<LogItem>
|
||||||
private lateinit var topAppBar: MaterialToolbar
|
private lateinit var topAppBar: MaterialToolbar
|
||||||
private lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
private lateinit var logViewModel: LogViewModel
|
private lateinit var logViewModel: LogViewModel
|
||||||
|
private var selectedObjects: ArrayList<LogItem>? = null
|
||||||
|
private var actionMode : ActionMode? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|
@ -39,6 +52,8 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
||||||
): View? {
|
): View? {
|
||||||
mainActivity = activity as MainActivity
|
mainActivity = activity as MainActivity
|
||||||
mainActivity.hideBottomNavigation()
|
mainActivity.hideBottomNavigation()
|
||||||
|
items = mutableListOf()
|
||||||
|
selectedObjects = arrayListOf()
|
||||||
return inflater.inflate(R.layout.fragment_download_log_list, container, false)
|
return inflater.inflate(R.layout.fragment_download_log_list, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,11 +70,18 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
||||||
recyclerView = view.findViewById(R.id.logs_recyclerview)
|
recyclerView = view.findViewById(R.id.logs_recyclerview)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
recyclerView.adapter = downloadLogAdapter
|
recyclerView.adapter = downloadLogAdapter
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||||
|
}
|
||||||
|
|
||||||
noResults = view.findViewById(R.id.no_results)
|
noResults = view.findViewById(R.id.no_results)
|
||||||
noResults.visibility = View.GONE
|
noResults.visibility = View.GONE
|
||||||
|
|
||||||
logViewModel = ViewModelProvider(this)[LogViewModel::class.java]
|
logViewModel = ViewModelProvider(this)[LogViewModel::class.java]
|
||||||
logViewModel.items.observe(viewLifecycleOwner) {
|
logViewModel.items.observe(viewLifecycleOwner) {
|
||||||
|
items = it.toMutableList()
|
||||||
if (it.isEmpty()) noResults.visibility = View.VISIBLE
|
if (it.isEmpty()) noResults.visibility = View.VISIBLE
|
||||||
else noResults.visibility = View.GONE
|
else noResults.visibility = View.GONE
|
||||||
downloadLogAdapter.submitList(it)
|
downloadLogAdapter.submitList(it)
|
||||||
|
|
@ -89,9 +111,9 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(item: LogItem) {
|
override fun onItemClick(itemID: Long) {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putLong("logID", item.id)
|
bundle.putLong("logID", itemID)
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
R.id.downloadLogFragment,
|
R.id.downloadLogFragment,
|
||||||
bundle
|
bundle
|
||||||
|
|
@ -108,4 +130,151 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
||||||
}
|
}
|
||||||
deleteDialog.show()
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||||
|
val item = items.find { it.id == itemID }
|
||||||
|
if (isChecked) {
|
||||||
|
selectedObjects!!.add(item!!)
|
||||||
|
if (actionMode == null){
|
||||||
|
actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||||
|
|
||||||
|
}else{
|
||||||
|
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedObjects!!.remove(item)
|
||||||
|
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
if (selectedObjects!!.isEmpty()){
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val contextualActionBar = object : ActionMode.Callback {
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
mode!!.menuInflater.inflate(R.menu.logs_menu_context, menu)
|
||||||
|
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(
|
||||||
|
mode: ActionMode?,
|
||||||
|
menu: Menu?
|
||||||
|
): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(
|
||||||
|
mode: ActionMode?,
|
||||||
|
item: MenuItem?
|
||||||
|
): Boolean {
|
||||||
|
return when (item!!.itemId) {
|
||||||
|
R.id.delete_results -> {
|
||||||
|
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||||
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
|
for (obj in selectedObjects!!){
|
||||||
|
logViewModel.delete(obj)
|
||||||
|
}
|
||||||
|
clearCheckedItems()
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.select_all -> {
|
||||||
|
downloadLogAdapter.checkAll(items)
|
||||||
|
selectedObjects?.clear()
|
||||||
|
items.forEach { selectedObjects?.add(it) }
|
||||||
|
mode?.title = getString(R.string.all_items_selected)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.invert_selected -> {
|
||||||
|
downloadLogAdapter.invertSelected(items)
|
||||||
|
val invertedList = arrayListOf<LogItem>()
|
||||||
|
items.forEach {
|
||||||
|
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||||
|
}
|
||||||
|
selectedObjects?.clear()
|
||||||
|
selectedObjects?.addAll(invertedList)
|
||||||
|
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
if (invertedList.isEmpty()) actionMode?.finish()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
actionMode = null
|
||||||
|
clearCheckedItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearCheckedItems(){
|
||||||
|
downloadLogAdapter.clearCheckeditems()
|
||||||
|
selectedObjects?.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
|
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
||||||
|
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
val position = viewHolder.bindingAdapterPosition
|
||||||
|
when (direction) {
|
||||||
|
ItemTouchHelper.LEFT -> {
|
||||||
|
val deletedItem = items[position]
|
||||||
|
logViewModel.delete(deletedItem)
|
||||||
|
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(getString(R.string.undo)) {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
|
logViewModel.insert(deletedItem)
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChildDraw(
|
||||||
|
c: Canvas,
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
dX: Float,
|
||||||
|
dY: Float,
|
||||||
|
actionState: Int,
|
||||||
|
isCurrentlyActive: Boolean
|
||||||
|
) {
|
||||||
|
RecyclerViewSwipeDecorator.Builder(
|
||||||
|
requireContext(),
|
||||||
|
c,
|
||||||
|
recyclerView,
|
||||||
|
viewHolder,
|
||||||
|
dX,
|
||||||
|
dY,
|
||||||
|
actionState,
|
||||||
|
isCurrentlyActive
|
||||||
|
)
|
||||||
|
.addSwipeLeftBackgroundColor(Color.RED)
|
||||||
|
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
|
||||||
|
.create()
|
||||||
|
.decorate()
|
||||||
|
super.onChildDraw(
|
||||||
|
c,
|
||||||
|
recyclerView,
|
||||||
|
viewHolder,
|
||||||
|
dX,
|
||||||
|
dY,
|
||||||
|
actionState,
|
||||||
|
isCurrentlyActive
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +200,10 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
version = findPreference("version")
|
version = findPreference("version")
|
||||||
version!!.summary = "${BuildConfig.VERSION_NAME} [${Build.SUPPORTED_ABIS[0]}] ${BuildConfig.BUILD_TYPE}"
|
val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir
|
||||||
|
val installedABI = Build.SUPPORTED_ABIS.first { nativeLibraryDir!!.contains(it) }
|
||||||
|
|
||||||
|
version!!.summary = "${BuildConfig.VERSION_NAME} [${installedABI}] ${BuildConfig.BUILD_TYPE}"
|
||||||
|
|
||||||
version!!.onPreferenceClickListener =
|
version!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ import android.widget.Toast
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
|
|
@ -560,9 +562,16 @@ class InfoUtil(private val context: Context) {
|
||||||
val formats : ArrayList<Format> = ArrayList()
|
val formats : ArrayList<Format> = ArrayList()
|
||||||
for (f in 0 until jsonArray.length()){
|
for (f in 0 until jsonArray.length()){
|
||||||
val format = jsonArray.getJSONObject(f)
|
val format = jsonArray.getJSONObject(f)
|
||||||
if (format.has("filesize") && format.get("filesize") == "None"){
|
if (format.has("filesize")){
|
||||||
format.remove("filesize")
|
if (format.get("filesize") == "None"){
|
||||||
format.put("filesize", 0)
|
format.remove("filesize")
|
||||||
|
format.put("filesize", 0)
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
val size = format.get("filesize").toString().toFloat()
|
||||||
|
format.remove("filesize")
|
||||||
|
format.put("filesize", size)
|
||||||
|
}catch (ignored: Exception){}
|
||||||
}
|
}
|
||||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
if (format.has("format_note")){
|
if (format.has("format_note")){
|
||||||
|
|
@ -626,9 +635,16 @@ class InfoUtil(private val context: Context) {
|
||||||
try{
|
try{
|
||||||
if (format.getString("filesize") == "None") continue
|
if (format.getString("filesize") == "None") continue
|
||||||
}catch (e: Exception) { continue }
|
}catch (e: Exception) { continue }
|
||||||
if (format.has("filesize") && format.get("filesize") == "None"){
|
if (format.has("filesize")){
|
||||||
format.remove("filesize")
|
if (format.get("filesize") == "None"){
|
||||||
format.put("filesize", 0)
|
format.remove("filesize")
|
||||||
|
format.put("filesize", 0)
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
val size = format.get("filesize").toString().toFloat()
|
||||||
|
format.remove("filesize")
|
||||||
|
format.put("filesize", size)
|
||||||
|
}catch (ignored: Exception){}
|
||||||
}
|
}
|
||||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
if ( !formatProper!!.format_note.contains("audio only", true)) {
|
if ( !formatProper!!.format_note.contains("audio only", true)) {
|
||||||
|
|
@ -733,9 +749,16 @@ class InfoUtil(private val context: Context) {
|
||||||
if (formatsInJSON != null) {
|
if (formatsInJSON != null) {
|
||||||
for (f in 0 until formatsInJSON.length()){
|
for (f in 0 until formatsInJSON.length()){
|
||||||
val format = formatsInJSON.getJSONObject(f)
|
val format = formatsInJSON.getJSONObject(f)
|
||||||
if (format.has("filesize") && format.get("filesize") == "None"){
|
if (format.has("filesize")){
|
||||||
format.remove("filesize")
|
if (format.get("filesize") == "None"){
|
||||||
format.put("filesize", 0)
|
format.remove("filesize")
|
||||||
|
format.put("filesize", 0)
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
val size = format.get("filesize").toString().toFloat()
|
||||||
|
format.remove("filesize")
|
||||||
|
format.put("filesize", size)
|
||||||
|
}catch (ignored: Exception){}
|
||||||
}
|
}
|
||||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
if (formatProper.format_note == null) continue
|
if (formatProper.format_note == null) continue
|
||||||
|
|
@ -768,7 +791,7 @@ class InfoUtil(private val context: Context) {
|
||||||
items.add(ResultItem(0,
|
items.add(ResultItem(0,
|
||||||
url,
|
url,
|
||||||
title,
|
title,
|
||||||
author!!,
|
author,
|
||||||
duration,
|
duration,
|
||||||
thumb!!,
|
thumb!!,
|
||||||
website,
|
website,
|
||||||
|
|
@ -844,7 +867,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}else{
|
}else{
|
||||||
jsonObject.getString("title")
|
jsonObject.getString("title")
|
||||||
},
|
},
|
||||||
author!!,
|
author,
|
||||||
duration,
|
duration,
|
||||||
thumb!!,
|
thumb!!,
|
||||||
jsonObject.getString("extractor"),
|
jsonObject.getString("extractor"),
|
||||||
|
|
@ -969,6 +992,244 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||||
|
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + downloadItem.id)
|
||||||
|
|
||||||
|
val url = downloadItem.url
|
||||||
|
val request = YoutubeDLRequest(url)
|
||||||
|
val type = downloadItem.type
|
||||||
|
|
||||||
|
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
||||||
|
if (aria2) {
|
||||||
|
request.addOption("--downloader", "libaria2c.so")
|
||||||
|
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
|
||||||
|
} else {
|
||||||
|
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||||
|
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
||||||
|
}
|
||||||
|
|
||||||
|
val retries = sharedPreferences.getString("--retries", "")!!
|
||||||
|
val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!!
|
||||||
|
|
||||||
|
if(retries.isNotEmpty()) request.addOption("retries", retries)
|
||||||
|
if(fragmentRetries.isNotEmpty()) request.addOption("fragment-retries", fragmentRetries)
|
||||||
|
|
||||||
|
val limitRate = sharedPreferences.getString("limit_rate", "")
|
||||||
|
if (limitRate != "") request.addOption("-r", limitRate!!)
|
||||||
|
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||||
|
if (downloadItem.SaveThumb) {
|
||||||
|
request.addOption("--write-thumbnail")
|
||||||
|
request.addOption("--convert-thumbnails", "png")
|
||||||
|
}
|
||||||
|
if (!sharedPreferences.getBoolean("mtime", false)){
|
||||||
|
request.addOption("--no-mtime")
|
||||||
|
}
|
||||||
|
|
||||||
|
val sponsorBlockFilters : ArrayList<String> = when(downloadItem.type) {
|
||||||
|
DownloadViewModel.Type.audio -> {
|
||||||
|
downloadItem.audioPreferences.sponsorBlockFilters
|
||||||
|
}
|
||||||
|
//video
|
||||||
|
else -> {
|
||||||
|
downloadItem.videoPreferences.sponsorBlockFilters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sponsorBlockFilters.isNotEmpty()) {
|
||||||
|
val filters = java.lang.String.join(",", sponsorBlockFilters.filter { it.isNotBlank() })
|
||||||
|
if (filters.isNotBlank()) request.addOption("--sponsorblock-remove", filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(downloadItem.title.isNotBlank()){
|
||||||
|
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title.take(200)))
|
||||||
|
}
|
||||||
|
if (downloadItem.author.isNotBlank()){
|
||||||
|
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
|
||||||
|
}
|
||||||
|
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
||||||
|
if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader)s - %(title)s"
|
||||||
|
|
||||||
|
if (downloadItem.downloadSections.isNotBlank()){
|
||||||
|
downloadItem.downloadSections.split(";").forEach {
|
||||||
|
if (it.isBlank()) return@forEach
|
||||||
|
if (it.contains(":"))
|
||||||
|
request.addOption("--download-sections", "*$it")
|
||||||
|
else
|
||||||
|
request.addOption("--download-sections", it)
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("force_keyframes", false)){
|
||||||
|
request.addOption("--force-keyframes-at-cuts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloadItem.customFileNameTemplate += " %(section_title)s %(autonumber)s"
|
||||||
|
request.addOption("--output-na-placeholder", " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("use_audio_quality", false)){
|
||||||
|
request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadItem.extraCommands.isNotBlank()){
|
||||||
|
val conf = File(context.cacheDir.absolutePath + "/downloads/configExtraCommands${System.currentTimeMillis()}.txt")
|
||||||
|
conf.createNewFile()
|
||||||
|
conf.writeText(downloadItem.extraCommands)
|
||||||
|
request.addOption(
|
||||||
|
"--config-locations",
|
||||||
|
conf.absolutePath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("restrict_filenames", true)) {
|
||||||
|
request.addOption("--restrict-filenames")
|
||||||
|
}
|
||||||
|
|
||||||
|
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||||
|
if (cookiesFile.exists()){
|
||||||
|
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
val proxy = sharedPreferences.getString("proxy", "")
|
||||||
|
if (proxy!!.isNotBlank()){
|
||||||
|
request.addOption("--proxy", proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||||
|
if(keepCache){
|
||||||
|
request.addOption("--part")
|
||||||
|
request.addOption("--keep-fragments")
|
||||||
|
}
|
||||||
|
|
||||||
|
when(type){
|
||||||
|
DownloadViewModel.Type.audio -> {
|
||||||
|
val supportedContainers = context.resources.getStringArray(R.array.audio_containers)
|
||||||
|
|
||||||
|
var audioQualityId : String = downloadItem.format.format_id
|
||||||
|
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality) || audioQualityId == "best") audioQualityId = ""
|
||||||
|
else if (audioQualityId == context.getString(R.string.worst_quality) || audioQualityId == "worst") audioQualityId = "worstaudio"
|
||||||
|
|
||||||
|
val ext = downloadItem.container
|
||||||
|
if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId)
|
||||||
|
request.addOption("-x")
|
||||||
|
|
||||||
|
if(ext.isNotBlank()){
|
||||||
|
if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){
|
||||||
|
request.addOption("--audio-format", ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.addOption("--embed-metadata")
|
||||||
|
|
||||||
|
if (downloadItem.audioPreferences.embedThumb) {
|
||||||
|
request.addOption("--embed-thumbnail")
|
||||||
|
request.addOption("--convert-thumbnails", "jpg")
|
||||||
|
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
|
||||||
|
try {
|
||||||
|
val config = File(context.cacheDir.absolutePath + "/downloads/${downloadItem.id}/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
|
||||||
|
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) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
|
||||||
|
|
||||||
|
if (downloadItem.playlistTitle.isNotEmpty()) {
|
||||||
|
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
|
||||||
|
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s")
|
||||||
|
} else {
|
||||||
|
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||||
|
request.addOption("--split-chapters")
|
||||||
|
request.addOption("-P", tempFileDir.absolutePath)
|
||||||
|
}else{
|
||||||
|
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.video -> {
|
||||||
|
val supportedContainers = context.resources.getStringArray(R.array.video_containers)
|
||||||
|
|
||||||
|
if (downloadItem.videoPreferences.addChapters) {
|
||||||
|
request.addOption("--sponsorblock-mark", "all")
|
||||||
|
request.addOption("--embed-chapters")
|
||||||
|
}
|
||||||
|
if (downloadItem.videoPreferences.embedSubs) {
|
||||||
|
request.addOption("--embed-subs")
|
||||||
|
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
||||||
|
}
|
||||||
|
val defaultFormats = context.resources.getStringArray(R.array.video_formats)
|
||||||
|
|
||||||
|
if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()) request.addOption("--audio-multistreams")
|
||||||
|
|
||||||
|
var videoFormatID = downloadItem.format.format_id
|
||||||
|
Log.e(DownloadWorker.TAG, videoFormatID)
|
||||||
|
var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best"
|
||||||
|
if (videoFormatID.isNotEmpty()) {
|
||||||
|
if (videoFormatID == context.resources.getString(R.string.best_quality) || videoFormatID == "best") videoFormatID = "bestvideo"
|
||||||
|
else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst"
|
||||||
|
else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]"
|
||||||
|
|
||||||
|
formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty() && ! downloadItem.videoPreferences.removeAudio){
|
||||||
|
val audioIds = downloadItem.videoPreferences.audioFormatIDs.joinToString("+")
|
||||||
|
"$videoFormatID+$audioIds/best/$videoFormatID"
|
||||||
|
}else{
|
||||||
|
"$videoFormatID+bestaudio/best/$videoFormatID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.e(DownloadWorker.TAG, formatArgument)
|
||||||
|
request.addOption("-f", formatArgument)
|
||||||
|
val outputFormat = downloadItem.container
|
||||||
|
if(outputFormat.isNotEmpty() && outputFormat != "Default" && outputFormat != context.getString(R.string.defaultValue) && supportedContainers.contains(outputFormat)){
|
||||||
|
request.addOption("--merge-output-format", outputFormat.lowercase())
|
||||||
|
if (outputFormat != "webm") {
|
||||||
|
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||||
|
if (embedThumb) {
|
||||||
|
request.addOption("--embed-thumbnail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadItem.videoPreferences.writeSubs){
|
||||||
|
request.addOption("--write-subs")
|
||||||
|
request.addOption("--write-auto-subs")
|
||||||
|
request.addOption("--sub-format", "str/ass/best")
|
||||||
|
request.addOption("--convert-subtitles", "srt")
|
||||||
|
if (!downloadItem.videoPreferences.embedSubs) {
|
||||||
|
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadItem.videoPreferences.removeAudio){
|
||||||
|
request.addOption("--ppa", "ffmpeg:-an")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||||
|
request.addOption("--split-chapters")
|
||||||
|
request.addOption("-P", tempFileDir.absolutePath)
|
||||||
|
}else{
|
||||||
|
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.command -> {
|
||||||
|
request.addOption(
|
||||||
|
"--config-locations",
|
||||||
|
File(context.cacheDir.absolutePath + "/downloads/config${System.currentTimeMillis()}.txt").apply {
|
||||||
|
writeText(downloadItem.format.format_note)
|
||||||
|
}.absolutePath
|
||||||
|
)
|
||||||
|
request.addOption("-P", tempFileDir.absolutePath)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
private val pipedURL = sharedPreferences.getString("piped_instance", defaultPipedURL)?.ifEmpty { defaultPipedURL }?.removeSuffix("/")
|
private val pipedURL = sharedPreferences.getString("piped_instance", defaultPipedURL)?.ifEmpty { defaultPipedURL }?.removeSuffix("/")
|
||||||
|
|
||||||
class PlaylistTuple internal constructor(
|
class PlaylistTuple internal constructor(
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,10 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -42,6 +40,7 @@ class DownloadWorker(
|
||||||
itemId = inputData.getLong("id", 0)
|
itemId = inputData.getLong("id", 0)
|
||||||
if (itemId == 0L) return Result.failure()
|
if (itemId == 0L) return Result.failure()
|
||||||
|
|
||||||
|
|
||||||
val notificationUtil = NotificationUtil(context)
|
val notificationUtil = NotificationUtil(context)
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val dbManager = DBManager.getInstance(context)
|
val dbManager = DBManager.getInstance(context)
|
||||||
|
|
@ -52,7 +51,6 @@ class DownloadWorker(
|
||||||
val logRepo = LogRepository(dbManager.logDao)
|
val logRepo = LogRepository(dbManager.logDao)
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
|
||||||
val downloadItem: DownloadItem?
|
val downloadItem: DownloadItem?
|
||||||
try {
|
try {
|
||||||
downloadItem = repository.getItemByID(itemId)
|
downloadItem = repository.getItemByID(itemId)
|
||||||
|
|
@ -63,6 +61,15 @@ class DownloadWorker(
|
||||||
|
|
||||||
if (downloadItem.status != DownloadRepository.Status.Queued.toString()) return Result.failure()
|
if (downloadItem.status != DownloadRepository.Status.Queued.toString()) return Result.failure()
|
||||||
|
|
||||||
|
val pendingIntent = NavDeepLinkBuilder(context)
|
||||||
|
.setGraph(R.navigation.nav_graph)
|
||||||
|
.setDestination(R.id.downloadQueueMainFragment)
|
||||||
|
.createPendingIntent()
|
||||||
|
|
||||||
|
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||||
|
val foregroundInfo = ForegroundInfo(downloadItem.id.toInt(), notification)
|
||||||
|
setForegroundAsync(foregroundInfo)
|
||||||
|
|
||||||
Log.e(TAG, downloadItem.toString())
|
Log.e(TAG, downloadItem.toString())
|
||||||
|
|
||||||
runBlocking{
|
runBlocking{
|
||||||
|
|
@ -75,254 +82,28 @@ class DownloadWorker(
|
||||||
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pendingIntent = NavDeepLinkBuilder(context)
|
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
||||||
.setGraph(R.navigation.nav_graph)
|
|
||||||
.setDestination(R.id.downloadQueueMainFragment)
|
|
||||||
.createPendingIntent()
|
|
||||||
|
|
||||||
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
|
||||||
val foregroundInfo = ForegroundInfo(downloadItem.id.toInt(), notification)
|
|
||||||
setForegroundAsync(foregroundInfo)
|
|
||||||
|
|
||||||
|
|
||||||
val url = downloadItem.url
|
|
||||||
val request = YoutubeDLRequest(url)
|
|
||||||
val type = downloadItem.type
|
|
||||||
val downloadLocation = downloadItem.downloadPath
|
|
||||||
|
|
||||||
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + downloadItem.id)
|
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + downloadItem.id)
|
||||||
tempFileDir.delete()
|
tempFileDir.delete()
|
||||||
tempFileDir.mkdirs()
|
tempFileDir.mkdirs()
|
||||||
|
|
||||||
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
val downloadLocation = downloadItem.downloadPath
|
||||||
if (aria2) {
|
|
||||||
request.addOption("--downloader", "libaria2c.so")
|
|
||||||
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
|
|
||||||
} else {
|
|
||||||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
|
||||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
|
||||||
}
|
|
||||||
|
|
||||||
val retries = sharedPreferences.getString("--retries", "")!!
|
|
||||||
val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!!
|
|
||||||
|
|
||||||
if(retries.isNotEmpty()) request.addOption("retries", retries)
|
|
||||||
if(fragmentRetries.isNotEmpty()) request.addOption("fragment-retries", fragmentRetries)
|
|
||||||
|
|
||||||
val limitRate = sharedPreferences.getString("limit_rate", "")
|
|
||||||
if (limitRate != "") request.addOption("-r", limitRate!!)
|
|
||||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
|
||||||
if (downloadItem.SaveThumb) {
|
|
||||||
request.addOption("--write-thumbnail")
|
|
||||||
request.addOption("--convert-thumbnails", "png")
|
|
||||||
}
|
|
||||||
if (!sharedPreferences.getBoolean("mtime", false)){
|
|
||||||
request.addOption("--no-mtime")
|
|
||||||
}
|
|
||||||
|
|
||||||
val sponsorBlockFilters : ArrayList<String> = when(downloadItem.type) {
|
|
||||||
DownloadViewModel.Type.audio -> {
|
|
||||||
downloadItem.audioPreferences.sponsorBlockFilters
|
|
||||||
}
|
|
||||||
//video
|
|
||||||
else -> {
|
|
||||||
downloadItem.videoPreferences.sponsorBlockFilters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sponsorBlockFilters.isNotEmpty()) {
|
|
||||||
val filters = java.lang.String.join(",", sponsorBlockFilters.filter { it.isNotBlank() })
|
|
||||||
if (filters.isNotBlank()) request.addOption("--sponsorblock-remove", filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(downloadItem.title.isNotBlank()){
|
|
||||||
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title.take(200)))
|
|
||||||
}
|
|
||||||
if (downloadItem.author.isNotBlank()){
|
|
||||||
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
|
|
||||||
}
|
|
||||||
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
|
||||||
if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader)s - %(title)s"
|
|
||||||
|
|
||||||
if (downloadItem.downloadSections.isNotBlank()){
|
|
||||||
downloadItem.downloadSections.split(";").forEach {
|
|
||||||
if (it.isBlank()) return@forEach
|
|
||||||
if (it.contains(":"))
|
|
||||||
request.addOption("--download-sections", "*$it")
|
|
||||||
else
|
|
||||||
request.addOption("--download-sections", it)
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("force_keyframes", false)){
|
|
||||||
request.addOption("--force-keyframes-at-cuts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
downloadItem.customFileNameTemplate += " %(section_title)s %(autonumber)s"
|
|
||||||
request.addOption("--output-na-placeholder", " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_audio_quality", false)){
|
|
||||||
request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("restrict_filenames", true)) {
|
|
||||||
request.addOption("--restrict-filenames")
|
|
||||||
}
|
|
||||||
|
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
|
||||||
if (cookiesFile.exists()){
|
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
|
||||||
if (proxy!!.isNotBlank()){
|
|
||||||
request.addOption("--proxy", proxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||||
if(keepCache){
|
|
||||||
request.addOption("--part")
|
|
||||||
request.addOption("--keep-fragments")
|
|
||||||
}
|
|
||||||
|
|
||||||
when(type){
|
|
||||||
DownloadViewModel.Type.audio -> {
|
|
||||||
val supportedContainers = context.resources.getStringArray(R.array.audio_containers)
|
|
||||||
|
|
||||||
var audioQualityId : String = downloadItem.format.format_id
|
|
||||||
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality) || audioQualityId == "best") audioQualityId = ""
|
|
||||||
else if (audioQualityId == context.getString(R.string.worst_quality) || audioQualityId == "worst") audioQualityId = "worstaudio"
|
|
||||||
|
|
||||||
val ext = downloadItem.container
|
|
||||||
if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId)
|
|
||||||
request.addOption("-x")
|
|
||||||
|
|
||||||
if(ext.isNotBlank()){
|
|
||||||
if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){
|
|
||||||
request.addOption("--audio-format", ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.addOption("--embed-metadata")
|
|
||||||
|
|
||||||
if (downloadItem.audioPreferences.embedThumb) {
|
|
||||||
request.addOption("--embed-thumbnail")
|
|
||||||
request.addOption("--convert-thumbnails", "jpg")
|
|
||||||
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
|
|
||||||
try {
|
|
||||||
val config = File(context.cacheDir.absolutePath + "/downloads/${downloadItem.id}/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
|
|
||||||
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) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
|
|
||||||
|
|
||||||
if (downloadItem.playlistTitle.isNotEmpty()) {
|
|
||||||
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
|
|
||||||
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s")
|
|
||||||
} else {
|
|
||||||
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
|
||||||
request.addOption("--split-chapters")
|
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
|
||||||
}else{
|
|
||||||
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
DownloadViewModel.Type.video -> {
|
|
||||||
val supportedContainers = context.resources.getStringArray(R.array.video_containers)
|
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.addChapters) {
|
|
||||||
request.addOption("--sponsorblock-mark", "all")
|
|
||||||
request.addOption("--embed-chapters")
|
|
||||||
}
|
|
||||||
if (downloadItem.videoPreferences.embedSubs) {
|
|
||||||
request.addOption("--embed-subs")
|
|
||||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
|
||||||
}
|
|
||||||
val defaultFormats = context.resources.getStringArray(R.array.video_formats)
|
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()) request.addOption("--audio-multistreams")
|
|
||||||
|
|
||||||
var videoFormatID = downloadItem.format.format_id
|
|
||||||
Log.e(TAG, videoFormatID)
|
|
||||||
var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best"
|
|
||||||
if (videoFormatID.isNotEmpty()) {
|
|
||||||
if (videoFormatID == context.resources.getString(R.string.best_quality) || videoFormatID == "best") videoFormatID = "bestvideo"
|
|
||||||
else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst"
|
|
||||||
else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]"
|
|
||||||
|
|
||||||
formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty() && ! downloadItem.videoPreferences.removeAudio){
|
|
||||||
val audioIds = downloadItem.videoPreferences.audioFormatIDs.joinToString("+")
|
|
||||||
"$videoFormatID+$audioIds/best/$videoFormatID"
|
|
||||||
}else{
|
|
||||||
"$videoFormatID+bestaudio/best/$videoFormatID"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.e(TAG, formatArgument)
|
|
||||||
request.addOption("-f", formatArgument)
|
|
||||||
val outputFormat = downloadItem.container
|
|
||||||
if(outputFormat.isNotEmpty() && outputFormat != "Default" && outputFormat != context.getString(R.string.defaultValue) && supportedContainers.contains(outputFormat)){
|
|
||||||
request.addOption("--merge-output-format", outputFormat.lowercase())
|
|
||||||
if (outputFormat != "webm") {
|
|
||||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
|
||||||
if (embedThumb) {
|
|
||||||
request.addOption("--embed-thumbnail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.writeSubs){
|
|
||||||
request.addOption("--write-subs")
|
|
||||||
request.addOption("--write-auto-subs")
|
|
||||||
request.addOption("--sub-format", "str/ass/best")
|
|
||||||
request.addOption("--convert-subtitles", "srt")
|
|
||||||
if (!downloadItem.videoPreferences.embedSubs) {
|
|
||||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.removeAudio){
|
|
||||||
request.addOption("--ppa", "ffmpeg:-an")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
|
||||||
request.addOption("--split-chapters")
|
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
|
||||||
}else{
|
|
||||||
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
DownloadViewModel.Type.command -> {
|
|
||||||
request.addOption(
|
|
||||||
"--config-locations",
|
|
||||||
File(context.cacheDir.absolutePath + "/downloads/config${System.currentTimeMillis()}.txt").apply {
|
|
||||||
writeText(downloadItem.format.format_note)
|
|
||||||
}.absolutePath
|
|
||||||
)
|
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||||
|
|
||||||
val logItem = LogItem(
|
val logItem = LogItem(
|
||||||
0,
|
0,
|
||||||
downloadItem.title,
|
downloadItem.title.ifEmpty { downloadItem.url },
|
||||||
"Downloading:\n" +
|
"Downloading:\n" +
|
||||||
"Title: ${downloadItem.title}\n" +
|
"Title: ${downloadItem.title}\n" +
|
||||||
"URL: ${downloadItem.url}\n" +
|
"URL: ${downloadItem.url}\n" +
|
||||||
"Type: ${downloadItem.type}\n" +
|
"Type: ${downloadItem.type}\n" +
|
||||||
"Format: ${downloadItem.format}\n\n" +
|
"Format: ${downloadItem.format}\n\n" +
|
||||||
"Command: ${java.lang.String.join(" ", request.buildCommand())}\n\n"
|
"Command: ${java.lang.String.join(" ", request.buildCommand())}\n\n",
|
||||||
|
downloadItem.format,
|
||||||
|
downloadItem.type,
|
||||||
|
System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,10 @@ import androidx.work.workDataOf
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
|
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
|
@ -83,10 +85,13 @@ class TerminalDownloadWorker(
|
||||||
|
|
||||||
val logItem = LogItem(
|
val logItem = LogItem(
|
||||||
0,
|
0,
|
||||||
"Terminal Download at " + Calendar.getInstance().time,
|
"Terminal Download",
|
||||||
"Downloading:\n" +
|
"Downloading:\n" +
|
||||||
"Terminal Download\n" +
|
"Terminal Download\n" +
|
||||||
"Command: ${command}\n\n"
|
"Command: ${command}\n\n",
|
||||||
|
Format(),
|
||||||
|
DownloadViewModel.Type.command,
|
||||||
|
System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" >
|
app:layout_constraintTop_toTopOf="parent" >
|
||||||
|
|
||||||
<com.google.android.exoplayer2.ui.StyledPlayerView
|
|
||||||
|
|
||||||
|
<androidx.media3.ui.PlayerView
|
||||||
android:id="@+id/video_view"
|
android:id="@+id/video_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
@ -159,7 +161,7 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/colon">
|
app:layout_constraintStart_toEndOf="@+id/colon">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="text" />
|
android:inputType="text" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/download_log_item_constraint"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="15dp"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:clickable="true"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:textSize="18sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/delete"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
android:focusable="true" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/delete"
|
|
||||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:padding="15dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:icon="@drawable/ic_delete_all"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
90
app/src/main/res/layout/extra_commands_bottom_sheet.xml
Normal file
90
app/src/main/res/layout/extra_commands_bottom_sheet.xml
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/use_extra_commands"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/current"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/currentTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/current"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/currentText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/textInputLayout"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/current">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/command"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:maxLines="2000" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/okButton"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/ok"
|
||||||
|
app:icon="@drawable/ic_check"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="@string/format"
|
android:text="@string/format"
|
||||||
android:textSize="12sp"
|
android:textSize="14sp"
|
||||||
android:layout_margin="20dp"
|
android:layout_margin="20dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,22 @@
|
||||||
</com.google.android.material.chip.ChipGroup>
|
</com.google.android.material.chip.ChipGroup>
|
||||||
|
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/extra_commands"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/use_extra_commands"
|
||||||
|
app:chipIconVisible="true"
|
||||||
|
app:chipIcon="@drawable/ic_terminal"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,22 @@
|
||||||
</com.google.android.material.chip.ChipGroup>
|
</com.google.android.material.chip.ChipGroup>
|
||||||
|
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/extra_commands"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/use_extra_commands"
|
||||||
|
app:chipIconVisible="true"
|
||||||
|
app:chipIcon="@drawable/ic_terminal"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
201
app/src/main/res/layout/log_card.xml
Normal file
201
app/src/main/res/layout/log_card.xml
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView android:id="@+id/log_card_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@android:color/transparent"
|
||||||
|
android:checkable="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:checkedIcon="@null"
|
||||||
|
app:strokeColor="?attr/colorPrimary"
|
||||||
|
app:cardPreventCornerOverlap="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||||
|
app:strokeWidth="0dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/log_icon"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/ic_baseline_file_open_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||||
|
app:layout_constraintHorizontal_weight="0.3"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/download_item_data"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:clickable="false"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_weight="0.7"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/log_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="false"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/action_button"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="title,action_button" />
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:clickable="false"
|
||||||
|
android:paddingStart="5dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:scrollbars="none"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/barrier">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/downloadedTime"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/rounded_corner"
|
||||||
|
android:backgroundTint="?attr/colorPrimary"
|
||||||
|
android:clickable="false"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/format_note"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/rounded_corner"
|
||||||
|
android:backgroundTint="?attr/colorSecondary"
|
||||||
|
android:clickable="false"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLength="17"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/codec"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/rounded_corner"
|
||||||
|
android:backgroundTint="?attr/colorSecondary"
|
||||||
|
android:clickable="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/file_size"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/rounded_corner"
|
||||||
|
android:backgroundTint="?attr/colorSecondary"
|
||||||
|
android:clickable="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/download_type"
|
||||||
|
android:clickable="false"
|
||||||
|
style="?attr/materialIconButtonStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:icon="@drawable/ic_video"
|
||||||
|
app:iconTint="?attr/colorAccent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
23
app/src/main/res/menu/logs_menu_context.xml
Normal file
23
app/src/main/res/menu/logs_menu_context.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".MainActivity" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/delete_results"
|
||||||
|
android:title="@string/remove_results"
|
||||||
|
android:icon="@drawable/baseline_delete_24"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/select_all"
|
||||||
|
android:title="@string/select_all"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/invert_selected"
|
||||||
|
android:title="@string/invert_selected"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
|
|
||||||
|
|
@ -273,7 +273,7 @@
|
||||||
<string name="custom_audio_quality">Përdor cilësinë e personalizuar të audios</string>
|
<string name="custom_audio_quality">Përdor cilësinë e personalizuar të audios</string>
|
||||||
<string name="piped_instance">Shembull me tubacione</string>
|
<string name="piped_instance">Shembull me tubacione</string>
|
||||||
<string name="piped_instance_summary">Shkruani një Server API PIPED, aplikacioni mund të përdorë për kërkimet dhe formatet në youtube</string>
|
<string name="piped_instance_summary">Shkruani një Server API PIPED, aplikacioni mund të përdorë për kërkimet dhe formatet në youtube</string>
|
||||||
<string name="retries">Riprovon</string>
|
<string name="retries">Riprovimet</string>
|
||||||
<string name="fragment_retries">Riprovimet e fragmentit</string>
|
<string name="fragment_retries">Riprovimet e fragmentit</string>
|
||||||
<string name="format_order">Renditja e formatit</string>
|
<string name="format_order">Renditja e formatit</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -284,4 +284,7 @@
|
||||||
<string name="format_order">Format Order</string>
|
<string name="format_order">Format Order</string>
|
||||||
<string name="force_keyframes">Force Keyframes At Cuts</string>
|
<string name="force_keyframes">Force Keyframes At Cuts</string>
|
||||||
<string name="force_keyframes_summary">Accurate Video Cuts. Slower</string>
|
<string name="force_keyframes_summary">Accurate Video Cuts. Slower</string>
|
||||||
|
<string name="use_extra_commands">Add Extra Commands</string>
|
||||||
|
<string name="use_extra_commands_summary">For Audio / Video downloads. Add extra commands along with the GUI configuration</string>
|
||||||
|
<string name="current">Current Command</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -140,4 +140,13 @@
|
||||||
app:defaultValue=""
|
app:defaultValue=""
|
||||||
app:summary="@string/preferred_format_id_summary"
|
app:summary="@string/preferred_format_id_summary"
|
||||||
app:title="@string/preferred_format_id" />
|
app:title="@string/preferred_format_id" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="false"
|
||||||
|
app:icon="@drawable/ic_terminal"
|
||||||
|
app:key="use_extra_commands"
|
||||||
|
android:summary="@string/use_extra_commands_summary"
|
||||||
|
app:title="@string/use_extra_commands" />
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<Preference
|
<Preference
|
||||||
android:key="downloading"
|
android:key="downloading"
|
||||||
app:icon="@drawable/ic_downloads"
|
app:icon="@drawable/ic_downloads"
|
||||||
android:title="@string/downloading"/>
|
android:title="@string/downloads"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="processing"
|
android:key="processing"
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ buildscript {
|
||||||
retrofitVer = "2.9.0"
|
retrofitVer = "2.9.0"
|
||||||
kodeinVer = "7.16.0"
|
kodeinVer = "7.16.0"
|
||||||
navVer = "2.5.3"
|
navVer = "2.5.3"
|
||||||
media3_version = "1.0.0"
|
media3_version = "1.1.0"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue