diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt index 28a76a45..680a8de9 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt @@ -27,4 +27,8 @@ class YTDLPViewModel(private val application: Application) : AndroidViewModel(ap fun getVersion(channel: String) : String { return ytdlpUtil.getVersion(application, channel) } + + fun getFilenameTemplatePreview(item: DownloadItem, filenameTemplate: String) : String { + return ytdlpUtil.getFilenameTemplatePreview(item, filenameTemplate) + } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt index 8b335e76..06fd0ed6 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt @@ -32,6 +32,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type import com.deniscerri.ytdl.database.viewmodel.FormatViewModel import com.deniscerri.ytdl.database.viewmodel.ResultViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.util.Extensions.applyFilenameTemplateForCuts import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FormatUtil @@ -57,6 +58,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private private var activity: Activity? = null private lateinit var downloadViewModel : DownloadViewModel private lateinit var resultViewModel : ResultViewModel + private lateinit var ytdlpViewModel : YTDLPViewModel private lateinit var formatViewModel : FormatViewModel private lateinit var saveDir : TextInputLayout private lateinit var freeSpace : TextView @@ -80,6 +82,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private activity = getActivity() downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] + ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java] formatViewModel = ViewModelProvider(requireActivity())[FormatViewModel::class.java] genericAudioFormats = FormatUtil(requireContext()).getGenericAudioFormats(requireContext().resources) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) @@ -310,6 +313,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private UiUtil.configureAudio( view, requireActivity(), + ytdlpViewModel, listOf(downloadItem), embedThumbClicked = { downloadItem.audioPreferences.embedThumb = it diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index b04562ca..f02b9805 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -50,6 +50,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.FormatViewModel import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel import com.deniscerri.ytdl.database.viewmodel.ResultViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.receiver.ShareActivity import com.deniscerri.ytdl.ui.BaseActivity import com.deniscerri.ytdl.ui.adapter.ConfigureMultipleDownloadsAdapter @@ -83,6 +84,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure private lateinit var historyViewModel: HistoryViewModel private lateinit var commandTemplateViewModel: CommandTemplateViewModel private lateinit var resultViewModel: ResultViewModel + private lateinit var ytdlpViewModel: YTDLPViewModel private lateinit var formatViewModel: FormatViewModel private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter private lateinit var recyclerView: RecyclerView @@ -121,6 +123,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java] resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java] + ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java] formatViewModel = ViewModelProvider(requireActivity())[FormatViewModel::class.java] commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java] sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) @@ -539,6 +542,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure UiUtil.configureAudio( sheetView, requireActivity(), + ytdlpViewModel, items, embedThumbClicked = {enabled -> items.forEach { @@ -631,6 +635,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure UiUtil.configureVideo( sheetView, requireActivity(), + ytdlpViewModel, items, embedSubsClicked = {checked -> items.forEach { it.videoPreferences.embedSubs = checked } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt index 6547b130..973cc887 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt @@ -32,6 +32,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type import com.deniscerri.ytdl.database.viewmodel.FormatViewModel import com.deniscerri.ytdl.database.viewmodel.ResultViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.util.Extensions.applyFilenameTemplateForCuts import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FormatUtil @@ -59,6 +60,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private private lateinit var downloadViewModel : DownloadViewModel private lateinit var formatViewModel : FormatViewModel private lateinit var resultViewModel: ResultViewModel + private lateinit var ytdlpViewModel: YTDLPViewModel private lateinit var preferences: SharedPreferences private lateinit var shownFields: List lateinit var title : TextInputLayout @@ -85,6 +87,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] formatViewModel = ViewModelProvider(requireActivity())[FormatViewModel::class.java] + ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java] val formatUtil = FormatUtil(requireContext()) genericVideoFormats = formatUtil.getGenericVideoFormats(requireContext().resources) genericAudioFormats = formatUtil.getGenericAudioFormats(requireContext().resources) @@ -353,6 +356,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private UiUtil.configureVideo( view, requireActivity(), + ytdlpViewModel, listOf(downloadItem), embedSubsClicked = { downloadItem.videoPreferences.embedSubs = it diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt index 2fa211e8..b17a7e95 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt @@ -174,7 +174,7 @@ class FolderSettingsFragment : BaseSettingsFragment() { audioFilenameTemplate?.summary = preferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B") videoFilenameTemplate?.setOnPreferenceClickListener { - UiUtil.showFilenameTemplateDialog(requireActivity(), videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") { + UiUtil.showFilenameTemplateDialog(requireActivity(),videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") { editor.putString("file_name_template", it).apply() videoFilenameTemplate?.summary = it } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index aa8a3f96..131156c1 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -17,6 +17,7 @@ import android.content.SharedPreferences import android.net.Uri import android.os.Build import android.os.Environment +import android.os.Looper import android.text.Editable import android.text.TextWatcher import android.text.format.DateFormat @@ -35,6 +36,7 @@ import android.widget.CheckBox import android.widget.EditText import android.widget.LinearLayout import android.widget.PopupMenu +import android.widget.ProgressBar import android.widget.RadioButton import android.widget.TextView import android.widget.Toast @@ -105,6 +107,7 @@ import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale +import java.util.logging.Handler object UiUtil { @@ -1302,6 +1305,7 @@ object UiUtil { fun configureVideo( view: View, context: Activity, + ytdlpViewModel: YTDLPViewModel, items: List, embedSubsClicked : (Boolean) -> Unit, addChaptersClicked: (Boolean) -> Unit, @@ -1764,7 +1768,8 @@ object UiUtil { }else { "" } - showFilenameTemplateDialog(context, currentFilename) { + val itemContext = if (items.size == 1) items.first() else null + showFilenameTemplateDialog(context, currentFilename, ytdlpViewModel = ytdlpViewModel, itemContext = itemContext) { filenameTemplateSet(it) } } @@ -1791,6 +1796,7 @@ object UiUtil { fun configureAudio( view: View, context: Activity, + ytdlpViewModel: YTDLPViewModel, items: List, embedThumbClicked: (Boolean) -> Unit, cropThumbClicked: (Boolean) -> Unit, @@ -1867,7 +1873,8 @@ object UiUtil { }else { "" } - showFilenameTemplateDialog(context, currentFilename) { + val itemContext = if (items.size == 1) items.first() else null + showFilenameTemplateDialog(context, currentFilename, ytdlpViewModel = ytdlpViewModel, itemContext = itemContext) { filenameTemplateSet(it) } } @@ -2103,7 +2110,7 @@ object UiUtil { } @SuppressLint("RestrictedApi") - fun showFilenameTemplateDialog(context: Activity, currentFilename: String, dialogTitle: String = context.getString(R.string.file_name_template), filenameSelected: (f: String) -> Unit){ + fun showFilenameTemplateDialog(context: Activity, currentFilename: String, dialogTitle: String = context.getString(R.string.file_name_template), ytdlpViewModel: YTDLPViewModel? = null, itemContext: DownloadItem? = null, filenameSelected: (f: String) -> Unit){ val builder = MaterialAlertDialogBuilder(context) builder.setTitle(dialogTitle) val view = context.layoutInflater.inflate(R.layout.filename_template_dialog, null) @@ -2174,6 +2181,42 @@ object UiUtil { myTemplatesView.isVisible = true } + view.findViewById(R.id.filename_template_preview).isVisible = itemContext != null + val previewTemplateBtn = view.findViewById(R.id.filename_template_preview_btn) + val previewTemplateLoading = view.findViewById(R.id.filename_template_preview_loading) + val previewTemplateText = view.findViewById(R.id.filename_template_preview_text) + + previewTemplateBtn.setOnClickListener { + previewTemplateLoading.isVisible = true + previewTemplateBtn.isVisible = false + + CoroutineScope(Dispatchers.IO).launch { + val preview = ytdlpViewModel!!.getFilenameTemplatePreview(itemContext!!, editText.text.toString()) + + withContext(Dispatchers.Main) { + previewTemplateLoading.isVisible = false + previewTemplateText.isVisible = true + previewTemplateText.text = preview + } + } + } + + editText.addTextChangedListener(object : TextWatcher { + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + previewTemplateText.isVisible = false + previewTemplateLoading.isVisible = false + previewTemplateBtn.isVisible = true + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun afterTextChanged(s: Editable?) {} + }) + + if (currentFilename.isNotBlank() && itemContext != null) { + previewTemplateBtn.isVisible = true + previewTemplateBtn.performClick() + } + CoroutineScope(Dispatchers.IO).launch { //handle personal template chips val mychips = mutableListOf() @@ -2220,7 +2263,6 @@ object UiUtil { chips.add(tmp) } - withContext(Dispatchers.Main){ if (mychips.isNotEmpty()){ myTemplatesView.isVisible = true diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/YTDLPUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/YTDLPUtil.kt index 2f223fa2..65627b0c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/YTDLPUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/YTDLPUtil.kt @@ -41,6 +41,8 @@ import org.json.JSONArray import org.json.JSONObject import java.io.File import java.lang.reflect.Type +import java.security.MessageDigest +import java.util.Base64 import java.util.Locale import java.util.StringJoiner import java.util.UUID @@ -118,6 +120,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co }else{ request = YoutubeDLRequest(query) } + request.addWriteInfoJson(query) }else{ request = YoutubeDLRequest(emptyList()) when (searchEngine){ @@ -374,6 +377,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co return parseYTDLPListResults(results) } + suspend fun getFormatsForAll(urls: List, progress: (progress: ResultViewModel.MultipleFormatProgress) -> Unit) : Result>> { val formatCollection = mutableListOf>() @@ -456,7 +460,6 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co return Result.success(formatCollection) } - fun getFormats(url: String) : List { val request = YoutubeDLRequest(url) request.addOption("--print", "%(formats)s") @@ -583,7 +586,51 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co return Result.failure(e) } } + fun getVersion(context: Context, channel: String) : String { + if (listOf("stable", "nightly", "master").contains(channel)) { + return YoutubeDL.version(context) ?: "" + } + val req = YoutubeDLRequest(emptyList()) + req.addOption("--version") + return YoutubeDL.getInstance().execute(req).out.trim() + } + + @OptIn(ExperimentalStdlibApi::class) + private fun YoutubeDLRequest.addWriteInfoJson(url: String) { + val cachePath = "${FileUtil.getCachePath(context)}infojsons" + val infoJsonName = MessageDigest.getInstance("MD5").digest(url.toByteArray()).toHexString() + this.addCommands(listOf("--print-to-file", "video:%()#j", "${cachePath}/${infoJsonName}.info.json")) + } + + @OptIn(ExperimentalStdlibApi::class) + private fun getInfoJsonPath(url: String): String? { + val cachePath = "${FileUtil.getCachePath(context)}infojsons" + val infoJsonName = MessageDigest.getInstance("MD5").digest(url.toByteArray()).toHexString() + val infoJsonFile = File(cachePath).walkTopDown().firstOrNull { it.name == "${infoJsonName}.info.json" } + return infoJsonFile?.absolutePath + } + + fun getFilenameTemplatePreview(item: DownloadItem, filenameTemplate: String): String { + item.customFileNameTemplate = filenameTemplate + val request = buildYoutubeDLRequest(item) + request.addOption("--print", "%(filename)s") + request.addOption("--skip-download") + + val infoJsonPath = getInfoJsonPath(item.url) + if (infoJsonPath == null) { + request.addWriteInfoJson(item.url) + } else { + request.addOption("--load-info-json", infoJsonPath) + } + + try { + val response = YoutubeDL.execute(request) + return response.out.replace(FileUtil.getCachePath(context) + "${item.id}/", "").trim() + } catch (ex: Exception) { + return ex.message ?: "" + } + } fun parseYTDLRequestString(request : YoutubeDLRequest) : String { val arr = request.buildCommand().toMutableList() @@ -616,15 +663,6 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co } } - fun getVersion(context: Context, channel: String) : String { - if (listOf("stable", "nightly", "master").contains(channel)) { - return YoutubeDL.version(context) ?: "" - } - - val req = YoutubeDLRequest(emptyList()) - req.addOption("--version") - return YoutubeDL.getInstance().execute(req).out.trim() - } private fun YoutubeDLRequest.setYoutubeExtractorArgs(url: String?) { val extractorArgs = mutableListOf() @@ -858,11 +896,6 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co request.addOption("--proxy", proxy) } - val updateYTDLP = sharedPreferences.getBoolean("update_ytdlp_while_downloading", false) - if (updateYTDLP) { - request.addOption("-U") - } - val keepCache = sharedPreferences.getBoolean("keep_cache", false) if(keepCache){ request.addOption("--keep-fragments") diff --git a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt index f4ce9e15..4edd7118 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt @@ -169,6 +169,12 @@ class DownloadWorker( val noCache = writtenPath || (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()) val request = ytdlpUtil.buildYoutubeDLRequest(downloadItem) + + val updateYTDLP = sharedPreferences.getBoolean("update_ytdlp_while_downloading", false) + if (updateYTDLP) { + request.addOption("-U") + } + downloadItem.status = DownloadRepository.Status.Active.toString() CoroutineScope(Dispatchers.IO).launch { delay(1500) @@ -375,7 +381,6 @@ class DownloadWorker( FileUtil.deleteFile("${cachePath}/${infoJsonName}.info.json") } - if (logDownloads){ logRepo.update(it.message ?: "", logItem.id) }else{ diff --git a/app/src/main/res/layout/filename_template_dialog.xml b/app/src/main/res/layout/filename_template_dialog.xml index 49684b35..ff98748e 100644 --- a/app/src/main/res/layout/filename_template_dialog.xml +++ b/app/src/main/res/layout/filename_template_dialog.xml @@ -1,8 +1,8 @@ - @@ -31,6 +31,46 @@ + + + + + + + + + + Use playlist url given by user instead of the playlist_webpage_url tag from the json dump Update yt-dlp while downloading Add -U to yt-dlp to update it to latest before downloading + Preview filename