added other download cards and recyclerview for cards to be queued for download

pull/44/head
Denis Çerri 4 years ago
parent d124b8fb5f
commit 2371ab734d
No known key found for this signature in database
GPG Key ID: 96B3554AF5B193EE

@ -320,7 +320,7 @@
<PersistentState>
<option name="values">
<map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/schedule/baseline_schedule_24.xml" />
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/keyboard_double_arrow_down/baseline_keyboard_double_arrow_down_24.xml" />
</map>
</option>
</PersistentState>
@ -331,7 +331,7 @@
<option name="values">
<map>
<entry key="color" value="00a6ff" />
<entry key="outputName" value="ic_clock" />
<entry key="outputName" value="ic_concurrent_downloads" />
<entry key="sourceFile" value="C:\Users\denis\Desktop\adaptiveproduct_youtube_foreground_color_108 (1).svg" />
</map>
</option>

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "6c48acc2155f9a895971bd84c84b7d4e",
"identityHash": "5939be553c41327f4f64e1d62d008968",
"entities": [
{
"tableName": "results",
@ -198,7 +198,7 @@
},
{
"tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `audioQualityId` TEXT NOT NULL, `videoQualityId` TEXT NOT NULL, `customTemplateId` INTEGER NOT NULL, `audioFormat` TEXT NOT NULL, `videoFormat` TEXT NOT NULL, `formatDesc` TEXT NOT NULL, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `workID` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `audioQualityId` TEXT NOT NULL, `videoQualityId` TEXT NOT NULL, `customTemplateId` INTEGER NOT NULL, `audioFormat` TEXT NOT NULL, `videoFormat` TEXT NOT NULL, `formatDesc` TEXT NOT NULL, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `embedSubs` INTEGER NOT NULL, `addChapters` INTEGER NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `workID` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "url",
@ -303,6 +303,24 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "embedSubs",
"columnName": "embedSubs",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "addChapters",
"columnName": "addChapters",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "SaveThumb",
"columnName": "SaveThumb",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "status",
"columnName": "status",
@ -368,7 +386,7 @@
"views": [],
"setupQueries": [
"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, '6c48acc2155f9a895971bd84c84b7d4e')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5939be553c41327f4f64e1d62d008968')"
]
}
}

@ -9,6 +9,8 @@ import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.preference.PreferenceManager
import androidx.work.Configuration
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.color.DynamicColors
@ -23,12 +25,12 @@ import io.reactivex.observers.DisposableCompletableObserver
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import java.io.File
import java.util.concurrent.Executors
class App : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
notificationUtil = NotificationUtil(this)
createNotificationChannels()
PreferenceManager.setDefaultValues(
this,
@ -37,6 +39,15 @@ class App : Application() {
R.xml.root_preferences,
false
)
WorkManager.initialize(
this@App,
Configuration.Builder()
.setExecutor(Executors.newFixedThreadPool(
getSharedPreferences("root_preferences", MODE_PRIVATE)
.getInt("concurrent_downloads", 1)))
.build())
configureRxJavaErrorHandler()
Completable.fromAction { initLibraries() }.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -79,12 +90,11 @@ class App : Application() {
}
private fun createNotificationChannels() {
val notificationUtil = NotificationUtil(this)
notificationUtil.createNotificationChannel()
}
companion object {
private const val TAG = "App"
@SuppressLint("StaticFieldLeak")
lateinit var notificationUtil: NotificationUtil
}
}

@ -9,10 +9,7 @@ import android.os.Build
import android.os.IBinder
import android.os.SystemClock
import android.util.Log
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import androidx.work.*
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.service.DownloadInfo
import com.deniscerri.ytdlnis.service.IDownloaderListener
@ -32,6 +29,7 @@ import io.reactivex.schedulers.Schedulers
import java.io.File
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.regex.Pattern
@ -43,7 +41,6 @@ class DownloaderService : Service() {
private val downloadInfo = DownloadInfo()
private var downloadQueue = LinkedList<ResultItem>()
private val compositeDisposable = CompositeDisposable()
private val notificationUtil = App.notificationUtil
private var context: Context? = null
var downloadProcessID = "processID"
private var downloadNotificationID = 0
@ -59,11 +56,11 @@ class DownloaderService : Service() {
if (!downloadQueue.isEmpty()) {
title = downloadQueue.peek()?.title
}
notificationUtil.updateDownloadNotification(
downloadNotificationID,
line!!, progress.toInt(), downloadQueue.size, title,
notificationChannelID
)
// notificationUtil.updateDownloadNotification(
// downloadNotificationID,
// line!!, progress.toInt(), downloadQueue.size, title,
// notificationChannelID
// )
try {
for (activity in activities.keys) {
activity.runOnUiThread {

@ -0,0 +1,125 @@
package com.deniscerri.ytdlnis.adapter
import android.app.Activity
import android.graphics.Color
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) :
ListAdapter<DownloadItem?, ConfigureMultipleDownloadsAdapter.ViewHolder?>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
init {
this.onItemClickListener = onItemClickListener
this.activity = activity
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) :
RecyclerView.ViewHolder(itemView) {
val cardView: MaterialCardView
init {
cardView = itemView.findViewById(R.id.download_multiple_card_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.home_download_multiple_card, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
val imageURL = item!!.thumb
if (imageURL.isNotEmpty()) {
val uiHandler = Handler(Looper.getMainLooper())
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
thumbnail.setColorFilter(Color.argb(70, 0, 0, 0))
} else {
val uiHandler = Handler(Looper.getMainLooper())
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
thumbnail.setColorFilter(Color.argb(70, 0, 0, 0))
}
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.result_title)
var title = item.title
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title
// Author ----------------------------------
val author = card.findViewById<TextView>(R.id.author)
author.text = item.author
// Format
val format = card.findViewById<TextView>(R.id.format)
format.text = item.ext
// Quality
val quality = card.findViewById<TextView>(R.id.quality)
quality.text = item.formatDesc
// File Size
val fileSize = card.findViewById<TextView>(R.id.filesize)
fileSize.text = item.filesize
// Type Icon Button
val btn = card.findViewById<MaterialButton>(R.id.download_type)
when(item.type) {
"audio" -> {
btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music)
}
"video" -> {
btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video)
}
"command" -> {
btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_baseline_keyboard_arrow_right_24)
}
}
card.setOnClickListener {
onItemClickListener.onCardClick(item.id!!)
}
}
interface OnItemClickListener {
fun onCardClick(itemID: Int)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> =
object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
return oldItem.id === newItem.id
}
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
return oldItem.url == newItem.url
}
}
}
}

@ -15,8 +15,11 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Queued'")
fun getQueuedDownloads() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE workID=:workID")
fun getDownloadsByWorkId(workID: Int) : List<DownloadItem>
fun getDownloadByWorkId(workID: Int) : DownloadItem
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
fun getDownloadById(id: Int) : DownloadItem

@ -27,8 +27,10 @@ data class DownloadItem(
val embedSubs: Boolean,
val addChapters: Boolean,
val SaveThumb: Boolean,
var ext: String,
var filesize: String,
@ColumnInfo(defaultValue = "Queued")
val status: String,
var status: String,
val workID: Int
){
@PrimaryKey(autoGenerate = true)

@ -8,6 +8,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
val allDownloads : LiveData<List<DownloadItem>> = downloadDao.getAllDownloads()
val activeDownloads : LiveData<List<DownloadItem>> = downloadDao.getActiveDownloads()
enum class status {
Active, Queued, Errored
}
suspend fun insert(item: DownloadItem){
downloadDao.insert(item)
}
@ -20,9 +24,17 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.update(item)
}
fun getDownloadsByWorkID(workID: Int) : List<DownloadItem>{
return downloadDao.getDownloadsByWorkId(workID)
fun getDownloadByWorkID(workID: Int) : DownloadItem{
return downloadDao.getDownloadByWorkId(workID)
}
fun getQueuedDownloads() : List<DownloadItem>{
return downloadDao.getQueuedDownloads()
}
suspend fun setDownloadStatus(item: DownloadItem, status: status){
item.status = status.toString()
update(item);
}
}

@ -20,6 +20,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var downloadCard: SwitchPreferenceCompat? = null
private var apiKey: EditTextPreference? = null
private var concurrentFragments: SeekBarPreference? = null
private var concurrentDownloads: SeekBarPreference? = null
private var limitRate: EditTextPreference? = null
private var aria2: SwitchPreferenceCompat? = null
private var sponsorblockFilters: MultiSelectListPreference? = null
@ -55,6 +56,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
downloadCard = findPreference("download_card")
apiKey = findPreference("api_key")
concurrentFragments = findPreference("concurrent_fragments")
concurrentDownloads = findPreference("concurrent_downloads")
limitRate = findPreference("limit_rate")
aria2 = findPreference("aria2")
sponsorblockFilters = findPreference("sponsorblock_filter")
@ -83,6 +85,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.putBoolean("download_card", downloadCard!!.isChecked)
editor.putString("api_key", apiKey!!.text)
editor.putInt("concurrent_fragments", concurrentFragments!!.value)
editor.putInt("concurrent_downloads", concurrentDownloads!!.value)
editor.putString("limit_rate", limitRate!!.text)
editor.putBoolean("aria2", aria2!!.isChecked)
editor.putStringSet("sponsorblock_filters", sponsorblockFilters!!.values)
@ -159,6 +162,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.apply()
true
}
concurrentDownloads!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
val value = newValue.toString().toInt()
editor.putInt("concurrent_downloads", value)
editor.apply()
true
}
limitRate!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
editor.putString("limit_rate", newValue.toString())

@ -9,21 +9,21 @@ import android.os.SystemClock
import android.provider.DocumentsContract
import android.util.Log
import android.widget.Toast
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.runBlocking
import java.io.File
import java.util.regex.Pattern
@ -31,8 +31,8 @@ import java.util.regex.Pattern
class DownloadWorker(
private val context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
) : Worker(context, workerParams) {
override fun doWork(): Result {
workID = inputData.getInt("workID", SystemClock.uptimeMillis().toInt())
val notificationUtil = NotificationUtil(context)
val dbManager = DBManager.getInstance(context)
@ -41,183 +41,182 @@ class DownloadWorker(
val commandTemplateDao = dbManager.commandTemplateDao
val historyDao = dbManager.historyDao
queue = repository.getDownloadsByWorkID(workID) as MutableList<DownloadItem>
val downloadItem = repository.getDownloadByWorkID(workID)
runBlocking{
repository.setDownloadStatus(downloadItem, DownloadRepository.status.Active)
}
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, queue[0].title, workID, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, workID, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
val foregroundInfo = ForegroundInfo(workID, notification)
setForeground(foregroundInfo)
while (queue.isNotEmpty()){
val item = dao.getDownloadById(queue[0].id!!)
queue.removeFirst()
val url = item.url
val request = YoutubeDLRequest(url)
val type = item.type
val downloadLocation = item.downloadPath
val tempFolder = StringBuilder(context.cacheDir.absolutePath + """/${item.title}##${item.type}""")
when(type){
"audio" -> tempFolder.append("##${item.audioQualityId}")
"video" -> tempFolder.append("##${item.videoQualityId}")
"command" -> tempFolder.append("##${item.customTemplateId}")
}
var tempFileDir = File(tempFolder.toString())
tempFileDir.delete()
tempFileDir.mkdir()
setForegroundAsync(foregroundInfo)
val url = downloadItem.url
val request = YoutubeDLRequest(url)
val type = downloadItem.type
val downloadLocation = downloadItem.downloadPath
val tempFolder = StringBuilder(context.cacheDir.absolutePath + """/${downloadItem.title}##${downloadItem.type}""")
when(type){
"audio" -> tempFolder.append("##${downloadItem.audioQualityId}")
"video" -> tempFolder.append("##${downloadItem.videoQualityId}")
"command" -> tempFolder.append("##${downloadItem.customTemplateId}")
}
var tempFileDir = File(tempFolder.toString())
tempFileDir.delete()
tempFileDir.mkdir()
val sharedPreferences = context.getSharedPreferences("root_preferences",
Service.MODE_PRIVATE
)
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 limitRate = sharedPreferences.getString("limit_rate", "")
if (limitRate != "") request.addOption("-r", limitRate!!)
val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false)
if (writeThumbnail) {
request.addOption("--write-thumbnail")
request.addOption("--convert-thumbnails", "png")
}
request.addOption("--no-mtime")
val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
if (sponsorBlockFilters!!.isNotEmpty()) {
val filters = java.lang.String.join(",", sponsorBlockFilters)
request.addOption("--sponsorblock-remove", filters)
val sharedPreferences = context.getSharedPreferences("root_preferences",
Service.MODE_PRIVATE
)
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 limitRate = sharedPreferences.getString("limit_rate", "")
if (limitRate != "") request.addOption("-r", limitRate!!)
val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false)
if (writeThumbnail) {
request.addOption("--write-thumbnail")
request.addOption("--convert-thumbnails", "png")
}
request.addOption("--no-mtime")
val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
if (sponsorBlockFilters!!.isNotEmpty()) {
val filters = java.lang.String.join(",", sponsorBlockFilters)
request.addOption("--sponsorblock-remove", filters)
}
when(type){
"audio" -> {
request.addOption("-x")
var audioQualityId : String = downloadItem.audioQualityId
if (audioQualityId == "0") audioQualityId = "ba"
var format = downloadItem.audioFormat
if (format.isEmpty()){
format = sharedPreferences.getString("audio_format", "")!!
}
request.addOption("-f", audioQualityId)
request.addOption("--audio-format", format)
request.addOption("--embed-metadata")
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) {
request.addOption("--embed-thumbnail")
request.addOption("--convert-thumbnails", "png")
try {
val config = File(context.cacheDir, "config" + downloadItem.title + "##" + downloadItem.audioQualityId + ".txt")
val configData =
"--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
config.writeText(configData)
request.addOption("--config", config.absolutePath)
} catch (ignored: Exception) {}
}
request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
request.addCommands(listOf("--replace-in-metadata", "title", ".*.", downloadItem.title))
request.addCommands(listOf("--replace-in-metadata", "uploader", ".*.", downloadItem.author))
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")
}
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
}
"video" -> {
if (downloadLocation == context.getString(R.string.video_path)){
tempFileDir = File(context.getString(R.string.video_path))
}
when(type){
"audio" -> {
request.addOption("-x")
var audioQualityId : String = item.audioQualityId
if (audioQualityId == "0") audioQualityId = "ba"
var format = item.audioFormat
if (format.isEmpty()){
format = sharedPreferences.getString("audio_format", "")!!
}
request.addOption("-f", audioQualityId)
request.addOption("--audio-format", format)
request.addOption("--embed-metadata")
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
if (addChapters) {
request.addOption("--sponsorblock-mark", "all")
}
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
if (embedSubs) {
request.addOption("--embed-subs", "")
}
var videoQualityId = downloadItem.videoQualityId
val audioQualityId = downloadItem.audioQualityId
if (videoQualityId.isEmpty()) videoQualityId = "bestvideo"
val formatArgument = StringBuilder(videoQualityId)
if (videoQualityId != "worst"){
if (audioQualityId != "0") formatArgument.append("+", audioQualityId, "/best")
}
request.addOption("-f", formatArgument.toString())
var format = downloadItem.videoFormat
if (format.isNotEmpty()) {
format = sharedPreferences.getString("video_format", "")!!
if (format != "DEFAULT") request.addOption("--merge-output-format", format)
}
if (format != "webm") {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) {
request.addOption("--embed-thumbnail")
request.addOption("--convert-thumbnails", "png")
try {
val config = File(context.cacheDir, "config" + item.title + "##" + item.audioQualityId + ".txt")
val configData =
"--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
config.writeText(configData)
request.addOption("--config", config.absolutePath)
} catch (ignored: Exception) {}
}
request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
request.addCommands(listOf("--replace-in-metadata", "title", ".*.", item.title))
request.addCommands(listOf("--replace-in-metadata", "uploader", ".*.", item.author))
if (item.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")
}
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
}
"video" -> {
if (downloadLocation == context.getString(R.string.video_path)){
tempFileDir = File(context.getString(R.string.video_path))
}
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
if (addChapters) {
request.addOption("--sponsorblock-mark", "all")
}
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
if (embedSubs) {
request.addOption("--embed-subs", "")
}
var videoQualityId = item.videoQualityId
val audioQualityId = item.audioQualityId
if (videoQualityId.isEmpty()) videoQualityId = "bestvideo"
val formatArgument = StringBuilder(videoQualityId)
if (videoQualityId != "worst"){
if (audioQualityId != "0") formatArgument.append("+", audioQualityId, "/best")
}
request.addOption("-f", formatArgument.toString())
var format = item.videoFormat
if (format.isNotEmpty()) {
format = sharedPreferences.getString("video_format", "")!!
if (format != "DEFAULT") request.addOption("--merge-output-format", format)
}
if (format != "webm") {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) {
request.addOption("--embed-thumbnail")
}
}
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
}
"command" -> {
val commandRegex = "\"([^\"]*)\"|(\\S+)"
val command = commandTemplateDao.getTemplateById(item.customTemplateId)
val m = Pattern.compile(commandRegex).matcher(command.content)
while (m.find()) {
if (m.group(1) != null) {
request.addOption(m.group(1)!!)
} else {
request.addOption(m.group(2)!!)
}
}
}
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
}
runCatching {
YoutubeDL.getInstance().execute(request, item.id.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt()))
var title: String? = context.getString(R.string.download_start)
if (queue.isNotEmpty()) {
title = queue[0].title
"command" -> {
val commandRegex = "\"([^\"]*)\"|(\\S+)"
val command = commandTemplateDao.getTemplateById(downloadItem.customTemplateId)
val m = Pattern.compile(commandRegex).matcher(command.content)
while (m.find()) {
if (m.group(1) != null) {
request.addOption(m.group(1)!!)
} else {
request.addOption(m.group(2)!!)
}
notificationUtil.updateDownloadNotification(
workID,
line, progress.toInt(), queue.size, title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
}
}.onSuccess {
//move file from internal to set download directory
moveFile(tempFileDir.absoluteFile, downloadLocation){ progress ->
setProgressAsync(workDataOf("progress" to progress))
}
//put download in history
val incognito = sharedPreferences.getBoolean("incognito", false)
if (!incognito) {
val unixtime = System.currentTimeMillis() / 1000
val historyItem = HistoryItem(item.url, item.title, item.author, item.duration, item.thumb, item.type, unixtime, item.downloadPath, item.website)
historyDao.insert(historyItem)
}
}
}
}.onFailure {
tempFileDir.delete()
if (BuildConfig.DEBUG) {
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
Log.e(DownloadWorker.TAG, context.getString(R.string.failed_download), it)
}
runCatching {
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt()))
val title: String = downloadItem.title
val queue = dao.getQueuedDownloads()
notificationUtil.updateDownloadNotification(
workID,
context.getString(R.string.failed_download), 0, 0, item.title,
line, progress.toInt(), queue.size, title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
return Result.failure()
}
}.onSuccess {
//move file from internal to set download directory
moveFile(tempFileDir.absoluteFile, downloadLocation){ progress ->
setProgressAsync(workDataOf("progress" to progress))
}
//put download in history
val incognito = sharedPreferences.getBoolean("incognito", false)
if (!incognito) {
val unixtime = System.currentTimeMillis() / 1000
val historyItem = HistoryItem(downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, downloadItem.downloadPath, downloadItem.website)
runBlocking {
historyDao.insert(historyItem)
}
}
}.onFailure {
tempFileDir.delete()
if (BuildConfig.DEBUG) {
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
Log.e(TAG, context.getString(R.string.failed_download), it)
}
notificationUtil.updateDownloadNotification(
workID,
context.getString(R.string.failed_download), 0, 0, downloadItem.title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
return Result.failure()
}
return Result.success()
}
@ -247,7 +246,6 @@ class DownloadWorker(
companion object {
var workID: Int = 0
const val TAG = "DownloadWorker"
lateinit var queue: MutableList<DownloadItem>
}
}

@ -0,0 +1,6 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M18,6.41l-1.41,-1.41l-4.59,4.58l-4.59,-4.58l-1.41,1.41l6,6z"/>
<path android:fillColor="?android:colorAccent" android:pathData="M18,13l-1.41,-1.41l-4.59,4.58l-4.59,-4.58l-1.41,1.41l6,6z"/>
</vector>

@ -165,7 +165,4 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<include layout="@layout/home_download_all_bottom_sheet"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/history_element_bottom_sheet"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="30dp"
android:orientation="vertical"
android:baselineAligned="false">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/title_textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/author_textinput"
android:layout_width="0dp"
android:layout_weight="45"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/author"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/audio_format"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:paddingStart="10dp"
android:layout_weight="45"
android:layout_height="wrap_content"
android:hint="@string/audio_format">
<AutoCompleteTextView
android:id="@+id/audio_format_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
app:simpleItems="@array/music_formats"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>

@ -7,6 +7,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -37,7 +38,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -87,33 +87,10 @@
</LinearLayout>
<LinearLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_multiple_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="end"
android:layout_margin="20dp"
android:orientation="horizontal">
<Button
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:id="@+id/bottomsheet_audio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_music"
android:text="@string/audio" />
<Button
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:id="@+id/bottomsheet_video_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video"
app:icon="@drawable/ic_video"
android:autoLink="all"/>
</LinearLayout>
android:layout_height="match_parent" />
</LinearLayout>

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/result_card_constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:id="@+id/result_relative_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,3:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.card.MaterialCardView
android:id="@+id/download_multiple_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="10dp"
app:cardElevation="10dp"
app:cardMaxElevation="12dp"
app:cardPreventCornerOverlap="true"
android:checkable="true"
app:strokeWidth="0dp"
android:layout_margin="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/result_image_view"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerPadding="5dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:orientation="vertical">
<TextView
android:id="@+id/result_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:textSize="17sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="2"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
<TextView
android:id="@+id/author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:paddingStart="10dp"
android:textSize="12sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="1.5"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:id="@+id/download_type"
android:layout_width="55dp"
android:layout_height="55dp"
app:cornerRadius="10dp"
android:layout_margin="10dp"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingEnd="12dp"
android:paddingBottom="10dp"
android:paddingStart="10dp"
android:gravity="end"
android:orientation="horizontal">
<TextView
android:id="@+id/format"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="10dp"
android:gravity="bottom"
android:textSize="15sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="1.5"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
<TextView
android:id="@+id/quality"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="10dp"
android:gravity="bottom"
android:textSize="15sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="1.5"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
<TextView
android:id="@+id/filesize"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="bottom"
android:textSize="15sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="1.5"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/history_element_bottom_sheet"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical"
>
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:textSize="25sp"
android:textColor="?attr/colorOnSurface"
android:layout_height="wrap_content"
android:text="@string/configure_download" />
<include layout="@layout/home_download_settings_components" />
<View style="@style/Divider.Horizontal"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="end"
android:orientation="horizontal">
<Button
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:id="@+id/bottomsheet_schedule_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_cancel"
android:text="@string/cancel" />
<Button
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:id="@+id/bottomsheet_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ok"
app:icon="@drawable/ic_check"
android:autoLink="all"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

@ -0,0 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/title_textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
app:errorEnabled="true"
android:hint="@string/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/author_textinput"
android:layout_width="0dp"
android:layout_weight="45"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/author"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/audio_format"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:paddingStart="10dp"
android:layout_weight="45"
android:layout_height="wrap_content"
android:hint="@string/audio_format">
<AutoCompleteTextView
android:id="@+id/audio_format_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
app:simpleItems="@array/music_formats"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/outputPath_textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/save_dir"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/audioChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:checkedIcon="@drawable/ic_check"
android:text="@string/audio"
app:chipIcon="@drawable/ic_music" />
<com.google.android.material.chip.Chip
android:id="@+id/videoChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video"
android:checkable="true"
app:checkedIcon="@drawable/ic_check"
app:chipIcon="@drawable/ic_video" />
<com.google.android.material.chip.Chip
android:id="@+id/commandChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/run_command"
android:checkable="true"
app:checkedIcon="@drawable/ic_check"
app:chipIcon="@drawable/ic_baseline_keyboard_arrow_right_24" />
</com.google.android.material.chip.ChipGroup>
<LinearLayout
android:id="@+id/adjust_commands"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:paddingBottom="10dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/adjust_templates" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/commands_chip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:selectionRequired="false"
app:singleLine="true"
app:singleSelection="false">
<com.google.android.material.chip.Chip
android:id="@+id/newTemplate"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/new_template"
app:chipIcon="@drawable/ic_plus" />
<com.google.android.material.chip.Chip
android:id="@+id/editSelected"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/edit_selected"
app:chipIcon="@drawable/ic_edit" />
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/adjust_video"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:paddingBottom="10dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/adjust_video" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
app:singleLine="true"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/embed_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/embed_subtitles"/>
<com.google.android.material.chip.Chip
android:id="@+id/add_chapters"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/add_chapter"/>
<com.google.android.material.chip.Chip
android:id="@+id/save_thumbnail"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/save_thumb"/>
<com.google.android.material.chip.Chip
android:id="@+id/remove_audio"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/remove_audio"/>
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -23,242 +23,7 @@
android:layout_height="wrap_content"
android:text="@string/download" />
<TextView
android:id="@+id/bottom_sheet_author"
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:paddingBottom="10dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/configure_download" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/title_textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
app:errorEnabled="true"
android:hint="@string/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/author_textinput"
android:layout_width="0dp"
android:layout_weight="45"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/author"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/audio_format"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:paddingStart="10dp"
android:layout_weight="45"
android:layout_height="wrap_content"
android:hint="@string/audio_format">
<AutoCompleteTextView
android:id="@+id/audio_format_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
app:simpleItems="@array/music_formats"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/outputPath_textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/save_dir"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/audioChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:checkedIcon="@drawable/ic_check"
android:text="@string/audio"
app:chipIcon="@drawable/ic_music" />
<com.google.android.material.chip.Chip
android:id="@+id/videoChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video"
android:checkable="true"
app:checkedIcon="@drawable/ic_check"
app:chipIcon="@drawable/ic_video" />
<com.google.android.material.chip.Chip
android:id="@+id/commandChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/run_command"
android:checkable="true"
app:checkedIcon="@drawable/ic_check"
app:chipIcon="@drawable/ic_baseline_keyboard_arrow_right_24" />
</com.google.android.material.chip.ChipGroup>
<LinearLayout
android:id="@+id/adjust_commands"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:paddingBottom="10dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/adjust_templates" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/commands_chip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:selectionRequired="false"
app:singleLine="true"
app:singleSelection="false">
<com.google.android.material.chip.Chip
android:id="@+id/newTemplate"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/new_template"
app:chipIcon="@drawable/ic_plus" />
<com.google.android.material.chip.Chip
android:id="@+id/editSelected"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/edit_selected"
app:chipIcon="@drawable/ic_edit" />
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/adjust_video"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:paddingBottom="10dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/adjust_video" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
app:singleLine="true"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/embed_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/embed_subtitles"/>
<com.google.android.material.chip.Chip
android:id="@+id/add_chapters"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/add_chapter"/>
<com.google.android.material.chip.Chip
android:id="@+id/save_thumbnail"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/save_thumb"/>
<com.google.android.material.chip.Chip
android:id="@+id/remove_audio"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/remove_audio"/>
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
<include layout="@layout/home_download_settings_components" />
<View style="@style/Divider.Horizontal"/>

@ -1,188 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/history_element_bottom_sheet"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:textSize="25sp"
android:textColor="?attr/colorOnSurface"
android:layout_height="wrap_content"
android:text="@string/download_video" />
<TextView
android:id="@+id/bottom_sheet_author"
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/configure_download" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="30dp"
android:orientation="vertical"
android:baselineAligned="false">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/title_textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/video_format"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_weight="45"
android:layout_height="wrap_content"
android:hint="@string/video_format">
<AutoCompleteTextView
android:id="@+id/video_format_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
app:simpleItems="@array/video_formats"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/video_quality"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:paddingStart="10dp"
android:layout_weight="45"
android:layout_height="wrap_content"
android:hint="@string/video_quality">
<AutoCompleteTextView
android:id="@+id/video_quality_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
app:simpleItems="@array/video_quality"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="20dp"
android:orientation="horizontal">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
app:singleLine="true"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/embed_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/embed_subtitles"/>
<com.google.android.material.chip.Chip
android:id="@+id/add_chapters"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/add_chapter"/>
<com.google.android.material.chip.Chip
android:id="@+id/save_thumbnail"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/save_thumb"/>
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="end"
android:layout_margin="20dp"
android:orientation="horizontal">
<Button
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:id="@+id/bottomsheet_cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_cancel"
android:text="@string/cancel" />
<Button
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:id="@+id/bottomsheet_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download"
app:icon="@drawable/ic_down"
android:autoLink="all"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">YTDLnis</string>
<string name="search">Ara</string>
<string name="remove_all"> Tümünü Kaldır</string>
<string name="remove_results"> Clear Results</string>
@ -65,9 +64,6 @@
<string name="choose_range_desc">Çalma listesinden indirmek için bir aralık seçin (her şeyi indirmemek için).</string>
<string name="last_cant_be_smaller_than_first">Son dizin ilkinden daha küçük olamaz.</string>
<string name="first_cant_be_larger_than_last">İlk dizin son dizinden büyük olamaz.</string>
<string name="music_path" translatable="false">/storage/emulated/0/Download</string>
<string name="video_path" translatable="false">/storage/emulated/0/Download</string>
<string name="command_path" translatable="false">/storage/emulated/0/Download</string>
<string name="concurrent_fragments_summary">Eşzamanlı olarak indirilecek DASH/HLS yerel video parçalarının sayısı</string>
<string name="concurrent_fragments">Eşzamanlı Parçalar</string>
<string name="downloading">İndirme</string>
@ -110,7 +106,6 @@
<string name="newest_first">En yeni</string>
<string name="oldest_first">En eski</string>
<item type="id" name="cancelDownload"/>
<string name="trendingPlaylist" translatable="false">ytdlnis-TRENDING</string>
<string name="currently_downloading">İndiriliyor</string>
<string name="remove_downloading">İndirmeleri İptal Et</string>
<string name="confirm_delete_downloading_desc">İndirilmekte olan tüm öğeleri kaldırın ve indirme hizmetini durdurun?</string>

@ -158,4 +158,6 @@
<string name="adjust_video">Adjust video</string>
<string name="remove_audio">Remove Audio</string>
<string name="schedule">Schedule</string>
<string name="concurrent_downloads">Concurrent Downloads</string>
<string name="concurrent_downloads_summary">Number of downloads executing at the same time</string>
</resources>

@ -56,6 +56,17 @@
app:summary="@string/concurrent_fragments_summary"
app:title="@string/concurrent_fragments" />
<SeekBarPreference
android:defaultValue="1"
android:icon="@drawable/ic_concurrent_downloads"
android:max="5"
app:key="concurrent_downloads"
app:min="1"
app:showSeekBarValue="true"
android:dependency="aria2"
app:summary="@string/concurrent_downloads_summary"
app:title="@string/concurrent_downloads" />
<EditTextPreference
android:icon="@drawable/ic_down"
app:key="limit_rate"

Loading…
Cancel
Save