more stuff
Added crop thumbnail as a preference Added ability to backup errored downloads Format cards will show collected filesize when multiple audio formats are chosen
This commit is contained in:
parent
0b487e10e6
commit
f259d31b7f
14 changed files with 117 additions and 38 deletions
|
|
@ -37,6 +37,9 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC")
|
||||
fun getProcessingDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getCancelledDownloadsList()
|
||||
}
|
||||
|
||||
fun getErroredDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getErroredDownloadsList()
|
||||
}
|
||||
|
||||
suspend fun deleteCancelled(){
|
||||
downloadDao.deleteCancelled()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
|||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Environment
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
|
|
@ -37,8 +41,11 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class DownloadViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||
private val repository : DownloadRepository
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
|
@ -58,6 +65,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
private val videoQualityPreference: String
|
||||
private val formatIDPreference: String
|
||||
private val resources : Resources
|
||||
enum class Type {
|
||||
audio, video, command
|
||||
}
|
||||
|
|
@ -80,7 +88,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString()
|
||||
|
||||
val videoFormat = App.instance.resources.getStringArray(R.array.video_formats)
|
||||
val confTmp = Configuration(application.resources.configuration)
|
||||
confTmp.locale = Locale(sharedPreferences.getString("app_language", "en")!!)
|
||||
val metrics = DisplayMetrics()
|
||||
resources = Resources(application.assets, metrics, confTmp)
|
||||
|
||||
|
||||
val videoFormat = resources.getStringArray(R.array.video_formats)
|
||||
var videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||
if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue)
|
||||
|
||||
|
|
@ -103,13 +117,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
var audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||
if (audioContainer == "Default") audioContainer = App.instance.getString(R.string.defaultValue)
|
||||
bestAudioFormat = Format(
|
||||
getApplication<App>().resources.getString(R.string.best_quality),
|
||||
resources.getString(R.string.best_quality),
|
||||
audioContainer!!,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
getApplication<App>().resources.getString(R.string.best_quality)
|
||||
resources.getString(R.string.best_quality)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -138,9 +152,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
val downloadPath = when(type){
|
||||
Type.audio -> sharedPreferences.getString("music_path", getApplication<App>().resources.getString(R.string.music_path))
|
||||
Type.video -> sharedPreferences.getString("video_path", getApplication<App>().resources.getString(R.string.video_path))
|
||||
else -> sharedPreferences.getString("command_path", getApplication<App>().resources.getString(R.string.command_path))
|
||||
Type.audio -> sharedPreferences.getString("music_path", File(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/Audio").absolutePath)
|
||||
Type.video -> sharedPreferences.getString("video_path", File(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/Video").absolutePath)
|
||||
else -> sharedPreferences.getString("command_path", File(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/Command").absolutePath)
|
||||
}
|
||||
|
||||
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
|
||||
|
|
@ -233,9 +247,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
val downloadPath = when(historyItem.type){
|
||||
Type.audio -> sharedPreferences.getString("music_path", getApplication<App>().resources.getString(R.string.music_path))
|
||||
Type.video -> sharedPreferences.getString("video_path", getApplication<App>().resources.getString(R.string.video_path))
|
||||
else -> sharedPreferences.getString("command_path", getApplication<App>().resources.getString(R.string.command_path))
|
||||
Type.audio -> sharedPreferences.getString("music_path", File(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/Audio").absolutePath)
|
||||
Type.video -> sharedPreferences.getString("video_path", File(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/Video").absolutePath)
|
||||
else -> sharedPreferences.getString("command_path", File(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/Command").absolutePath)
|
||||
}
|
||||
|
||||
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
|
||||
|
|
@ -283,10 +297,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
formats.first { !it.format_note.contains("audio", ignoreCase = true) && it.format_id == formatIDPreference }
|
||||
}catch (e: Exception){
|
||||
when (videoQualityPreference) {
|
||||
getApplication<App>().resources.getString(R.string.worst_quality) -> {
|
||||
"worst" -> {
|
||||
theFormats.first {!it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
getApplication<App>().resources.getString(R.string.best_quality) -> {
|
||||
"best" -> {
|
||||
theFormats.last {!it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -323,20 +337,20 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
fun getGenericAudioFormats() : MutableList<Format>{
|
||||
val audioFormats = App.instance.resources.getStringArray(R.array.audio_formats)
|
||||
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
||||
val formats = mutableListOf<Format>()
|
||||
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
||||
if (containerPreference == "Default") containerPreference = App.instance.getString(R.string.defaultValue)
|
||||
if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue)
|
||||
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
fun getGenericVideoFormats() : MutableList<Format>{
|
||||
val videoFormats = App.instance.resources.getStringArray(R.array.video_formats)
|
||||
val videoFormats = resources.getStringArray(R.array.video_formats)
|
||||
val formats = mutableListOf<Format>()
|
||||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
||||
if (containerPreference == "Default") containerPreference = application.getString(R.string.defaultValue)
|
||||
videoFormats.forEach { formats.add(Format(it, containerPreference!!,application.getString(R.string.defaultValue),"", "",0, it)) }
|
||||
if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue)
|
||||
videoFormats.forEach { formats.add(Format(it, containerPreference!!,resources.getString(R.string.defaultValue),"", "",0, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
|
|
@ -378,6 +392,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
return repository.getCancelledDownloads()
|
||||
}
|
||||
|
||||
fun getErrored() : List<DownloadItem> {
|
||||
return repository.getErroredDownloads()
|
||||
}
|
||||
|
||||
private fun cloneFormat(item: Format) : Format {
|
||||
val string = Gson().toJson(item, Format::class.java)
|
||||
return Gson().fromJson(string, Format::class.java)
|
||||
|
|
@ -407,10 +425,15 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
} != null) {
|
||||
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
||||
}else{
|
||||
it.id = lastDownloadId
|
||||
val insert = async {repository.insert(it)}
|
||||
val id = insert.await()
|
||||
it.id = id
|
||||
if (it.id == 0L){
|
||||
it.id = lastDownloadId
|
||||
val insert = async {repository.insert(it)}
|
||||
val id = insert.await()
|
||||
it.id = id
|
||||
}else{
|
||||
repository.update(it)
|
||||
}
|
||||
|
||||
queuedItems.add(it)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
}
|
||||
}
|
||||
formats = allFormats.first().toMutableList()
|
||||
uiUtil.populateFormatCard(formatCard, item.first(), item.drop(1).map { it.format_note })
|
||||
uiUtil.populateFormatCard(formatCard, item.first(), item.drop(1))
|
||||
downloadItem.format.container = container.editText?.text.toString()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
"downloads" -> json.add("downloads", backupHistory())
|
||||
"queued" -> json.add("queued", backupQueuedDownloads() )
|
||||
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
|
||||
"errored" -> json.add("errored", backupErroredDownloads() )
|
||||
"cookies" -> json.add("cookies", backupCookies() )
|
||||
"templates" -> json.add("templates", backupCommandTemplates() )
|
||||
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
|
||||
|
|
@ -273,6 +274,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupErroredDownloads() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getErrored()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupCookies() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
|
|
@ -436,6 +451,23 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
//erorred downloads restore
|
||||
if(json.has("errored")){
|
||||
val items = json.getAsJsonArray("errored")
|
||||
val errored = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
errored.add(item)
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(item)
|
||||
}
|
||||
}
|
||||
if(errored.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
|
||||
}
|
||||
}
|
||||
|
||||
//cookies restore
|
||||
if(json.has("cookies")){
|
||||
val items = json.getAsJsonArray("cookies")
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ package com.deniscerri.ytdlnis.util
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Looper
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
|
|
@ -282,12 +284,8 @@ class InfoUtil(private val context: Context) {
|
|||
var video: ResultItem? = null
|
||||
try {
|
||||
val id = obj.getString("videoId")
|
||||
val title = obj.getString("title").toString()
|
||||
title.replace("'s", "'")
|
||||
title.replace("&", "&")
|
||||
val author = obj.getString("author").toString()
|
||||
author.replace("'s", "'")
|
||||
author.replace("&", "&")
|
||||
val title = Html.fromHtml(obj.getString("title").toString()).toString()
|
||||
val author = Html.fromHtml(obj.getString("author").toString()).toString()
|
||||
|
||||
if (author.isBlank()) throw Exception()
|
||||
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@ import java.util.Calendar
|
|||
|
||||
class UiUtil(private val fileUtil: FileUtil) {
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun populateFormatCard(formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List<String>?){
|
||||
fun populateFormatCard(formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List<Format>?){
|
||||
formatCard.findViewById<TextView>(R.id.container).text = chosenFormat.container.uppercase()
|
||||
if (audioFormats.isNullOrEmpty()){
|
||||
formatCard.findViewById<TextView>(R.id.format_note).text = chosenFormat.format_note.uppercase()
|
||||
}else{
|
||||
val title = "${chosenFormat.format_note.uppercase()} + [${audioFormats.joinToString("/")}]"
|
||||
val title = "${chosenFormat.format_note.uppercase()} + [${audioFormats.joinToString("/") { it.format_note }}]"
|
||||
formatCard.findViewById<TextView>(R.id.format_note).text = title
|
||||
}
|
||||
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
||||
|
|
@ -77,7 +77,9 @@ class UiUtil(private val fileUtil: FileUtil) {
|
|||
formatCard.findViewById<TextView>(R.id.codec).visibility = View.VISIBLE
|
||||
formatCard.findViewById<TextView>(R.id.codec).text = codec
|
||||
}
|
||||
formatCard.findViewById<TextView>(R.id.file_size).text = fileUtil.convertFileSize(chosenFormat.filesize)
|
||||
var filesize = chosenFormat.filesize
|
||||
if (!audioFormats.isNullOrEmpty()) filesize += audioFormats.sumOf { it.filesize }
|
||||
formatCard.findViewById<TextView>(R.id.file_size).text = fileUtil.convertFileSize(filesize)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -184,19 +184,20 @@ class DownloadWorker(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
request.addOption("--embed-metadata")
|
||||
|
||||
if (downloadItem.audioPreferences.embedThumb) {
|
||||
request.addOption("--embed-thumbnail")
|
||||
request.addOption("--convert-thumbnails", "jpg")
|
||||
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) {}
|
||||
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")
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -702,6 +702,7 @@
|
|||
<item>@string/downloads</item>
|
||||
<item>@string/in_queue</item>
|
||||
<item>@string/cancelled</item>
|
||||
<item>@string/errored</item>
|
||||
<item>@string/cookies</item>
|
||||
<item>@string/command_templates</item>
|
||||
<item>@string/shortcuts</item>
|
||||
|
|
@ -713,6 +714,7 @@
|
|||
<item>downloads</item>
|
||||
<item>queued</item>
|
||||
<item>cancelled</item>
|
||||
<item>errored</item>
|
||||
<item>cookies</item>
|
||||
<item>templates</item>
|
||||
<item>shortcuts</item>
|
||||
|
|
|
|||
|
|
@ -269,4 +269,6 @@
|
|||
<string name="general">General</string>
|
||||
<string name="swipe_gestures">Swipe Gestures</string>
|
||||
<string name="swipe_gestures_summary">Swipe items for certain actions</string>
|
||||
<string name="crop_thumb">Crop Thumbnail</string>
|
||||
<string name="crop_thumb_summary">Crop the thumbnail into a square for audio downloads</string>
|
||||
</resources>
|
||||
|
|
@ -74,6 +74,15 @@
|
|||
app:summary="@string/embed_thumb_summary"
|
||||
app:title="@string/embed_thumb" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:dependency="embed_thumbnail"
|
||||
app:icon="@drawable/ic_cut"
|
||||
app:key="crop_thumbnail"
|
||||
app:summary="@string/crop_thumb_summary"
|
||||
app:title="@string/crop_thumb" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
|
|
|
|||
Loading…
Reference in a new issue