From 0485fb71e1f92c76cab6ad1a224d1a97fa3ac4e5 Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sat, 4 Oct 2025 08:18:28 +0200 Subject: [PATCH] fixes, fix app preferring auto dub instead of original audio --- app/build.gradle | 6 +- app/src/main/assets/build_fake_node/README.md | 14 ++ .../main/assets/build_fake_node/fake-node.c | 70 +++++++ app/src/main/java/com/deniscerri/ytdl/App.kt | 6 +- .../database/repository/ResultRepository.kt | 2 +- .../database/viewmodel/DownloadViewModel.kt | 2 +- .../ytdl/database/viewmodel/YTDLPViewModel.kt | 63 +------ .../ui/downloadcard/AddExtraCommandsDialog.kt | 2 +- .../downloadcard/ResultCardDetailsDialog.kt | 1 - .../ui/downloads/DownloadQueueMainFragment.kt | 6 - .../ui/downloads/QueuedDownloadsFragment.kt | 3 - .../deniscerri/ytdl/ui/more/MoreFragment.kt | 9 - .../more/settings/FolderSettingsFragment.kt | 6 +- .../java/com/deniscerri/ytdl/util/FileUtil.kt | 19 +- .../com/deniscerri/ytdl/util/FormatUtil.kt | 20 +- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 2 +- .../com/deniscerri/ytdl/util/UpdateUtil.kt | 1 - .../util/extractors/{ => ytdlp}/YTDLPUtil.kt | 42 +++-- .../extractors/ytdlp/webview/YTDLPWebview.kt | 176 ++++++++++++++++++ .../deniscerri/ytdl/work/DownloadWorker.kt | 5 +- .../ytdl/work/ObserveSourceWorker.kt | 2 +- 21 files changed, 319 insertions(+), 138 deletions(-) create mode 100644 app/src/main/assets/build_fake_node/README.md create mode 100644 app/src/main/assets/build_fake_node/fake-node.c rename app/src/main/java/com/deniscerri/ytdl/util/extractors/{ => ytdlp}/YTDLPUtil.kt (98%) create mode 100644 app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/webview/YTDLPWebview.kt diff --git a/app/build.gradle b/app/build.gradle index 60855f37..99281e16 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -149,9 +149,9 @@ dependencies { // implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer" - implementation "io.github.junkfood02.youtubedl-android:library:0.17.4" - implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.17.2" - implementation "io.github.junkfood02.youtubedl-android:aria2c:0.17.2" + implementation "io.github.junkfood02.youtubedl-android:library:0.18.0" + implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.18.0" + implementation "io.github.junkfood02.youtubedl-android:aria2c:0.18.0" implementation "androidx.appcompat:appcompat:$appCompatVer" implementation "androidx.constraintlayout:constraintlayout:2.2.1" diff --git a/app/src/main/assets/build_fake_node/README.md b/app/src/main/assets/build_fake_node/README.md new file mode 100644 index 00000000..8579cf1f --- /dev/null +++ b/app/src/main/assets/build_fake_node/README.md @@ -0,0 +1,14 @@ +# Set NDK path +NDK=/path/to/android-ndk + +# Target: arm64-v8a, min API 24 +$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -O2 -static -o android-js-arm64 fake-node.c + +# armeabi-v7a +$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi14-clang -O2 -static -o android-js-arm fake-node.c + +# x86 +$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android24-clang -O2 -static -o android-js-x86 fake-node.c + +# x86_64 +$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android24-clang -O2 -static -o android-js-x86_64 fake-node.c diff --git a/app/src/main/assets/build_fake_node/fake-node.c b/app/src/main/assets/build_fake_node/fake-node.c new file mode 100644 index 00000000..fe63ea12 --- /dev/null +++ b/app/src/main/assets/build_fake_node/fake-node.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include + +#define PORT 65953 +#define BUFSIZE 65536 + +int main(int argc, char *argv[]) { + char buffer[BUFSIZE]; + int sock; + struct sockaddr_in server_addr; + + // Collect JS code + char *code = NULL; + size_t code_len = 0; + + if (argc > 1) { + // Join argv[1..] as code (like "node -e 'code'") + size_t total = 0; + for (int i = 1; i < argc; i++) total += strlen(argv[i]) + 1; + code = malloc(total); + code[0] = '\0'; + for (int i = 1; i < argc; i++) { + strcat(code, argv[i]); + if (i < argc - 1) strcat(code, " "); + } + } else { + // Read from stdin + ssize_t n = read(STDIN_FILENO, buffer, BUFSIZE - 1); + if (n <= 0) return 1; + buffer[n] = '\0'; + code = strdup(buffer); + } + + // Open socket + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return 1; + } + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(PORT); + server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + perror("connect"); + close(sock); + return 1; + } + + // Send code + write(sock, code, strlen(code)); + write(sock, "\0", 1); + + // Receive result + ssize_t n = read(sock, buffer, BUFSIZE - 1); + if (n > 0) { + buffer[n] = '\0'; + printf("%s\n", buffer); + } + + close(sock); + free(code); + return 0; +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/App.kt b/app/src/main/java/com/deniscerri/ytdl/App.kt index 221c6ae8..ae83d087 100644 --- a/app/src/main/java/com/deniscerri/ytdl/App.kt +++ b/app/src/main/java/com/deniscerri/ytdl/App.kt @@ -7,6 +7,7 @@ import androidx.core.content.edit import androidx.preference.PreferenceManager import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.ThemeUtil +import com.deniscerri.ytdl.util.extractors.ytdlp.webview.YTDLPWebview import com.yausername.aria2c.Aria2c import com.yausername.ffmpeg.FFmpeg import com.yausername.youtubedl_android.YoutubeDL @@ -15,7 +16,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch -import java.util.* class App : Application() { @@ -30,8 +30,10 @@ class App : Application() { applicationScope.launch((Dispatchers.IO)) { try { createNotificationChannels() - initLibraries() + //init js interp server + //val jsServer = YTDLPWebview(this@App, 65953) + val appVer = sharedPreferences.getString("version", "")!! if(appVer.isEmpty() || appVer != BuildConfig.VERSION_NAME){ sharedPreferences.edit(commit = true){ diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt index 7d2f4de7..e4953f40 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt @@ -17,7 +17,7 @@ import com.deniscerri.ytdl.util.Extensions.isYoutubeWatchVideosURL import com.deniscerri.ytdl.util.Extensions.needsDataUpdating import com.deniscerri.ytdl.util.extractors.GoogleApiUtil import com.deniscerri.ytdl.util.extractors.newpipe.NewPipeUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil import com.deniscerri.ytdl.util.extractors.YoutubeApiUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt index 6bc7480e..28e10e4c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt @@ -41,7 +41,7 @@ import com.deniscerri.ytdl.util.Extensions.toListString import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FormatUtil import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil import com.deniscerri.ytdl.work.AlarmScheduler import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker 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 77c267f2..28a76a45 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 @@ -1,72 +1,11 @@ package com.deniscerri.ytdl.database.viewmodel import android.app.Application -import android.content.SharedPreferences -import android.content.res.Configuration -import android.content.res.Resources -import android.os.Build -import android.os.Parcelable -import android.util.DisplayMetrics import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asLiveData -import androidx.lifecycle.viewModelScope -import androidx.paging.PagingData -import androidx.paging.filter -import androidx.paging.map -import androidx.preference.PreferenceManager -import androidx.work.Data -import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import com.deniscerri.ytdl.App -import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.DBManager import com.deniscerri.ytdl.database.dao.CommandTemplateDao -import com.deniscerri.ytdl.database.dao.DownloadDao -import com.deniscerri.ytdl.database.models.AudioPreferences -import com.deniscerri.ytdl.database.models.CommandTemplate import com.deniscerri.ytdl.database.models.DownloadItem -import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple -import com.deniscerri.ytdl.database.models.DownloadItemSimple -import com.deniscerri.ytdl.database.models.Format -import com.deniscerri.ytdl.database.models.HistoryItem -import com.deniscerri.ytdl.database.models.ResultItem -import com.deniscerri.ytdl.database.models.VideoPreferences -import com.deniscerri.ytdl.database.repository.DownloadRepository -import com.deniscerri.ytdl.database.repository.HistoryRepository -import com.deniscerri.ytdl.database.repository.ResultRepository -import com.deniscerri.ytdl.ui.downloadcard.MultipleItemFormatTuple -import com.deniscerri.ytdl.util.Extensions.needsDataUpdating -import com.deniscerri.ytdl.util.Extensions.toListString -import com.deniscerri.ytdl.util.FileUtil -import com.deniscerri.ytdl.util.FormatUtil -import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil -import com.deniscerri.ytdl.work.AlarmScheduler -import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker -import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker -import com.google.gson.Gson -import com.yausername.youtubedl_android.YoutubeDL -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext -import kotlinx.parcelize.Parcelize -import java.io.File -import java.util.Locale +import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil class YTDLPViewModel(private val application: Application) : AndroidViewModel(application) { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt index 13760fb3..37551e83 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt @@ -22,7 +22,7 @@ import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.UiUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.elevation.SurfaceColors diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt index d5d9518b..498cb1eb 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt @@ -59,7 +59,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.elevation.SurfaceColors import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.snackbar.Snackbar -import com.yausername.youtubedl_android.YoutubeDL import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt index e1096e79..0c9e2af9 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt @@ -1,7 +1,5 @@ package com.deniscerri.ytdl.ui.downloads -import android.annotation.SuppressLint -import android.content.DialogInterface import android.content.SharedPreferences import android.os.Bundle import android.view.LayoutInflater @@ -20,18 +18,14 @@ import androidx.viewpager2.widget.ViewPager2 import androidx.work.WorkManager import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.R -import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.util.Extensions.createBadge import com.deniscerri.ytdl.util.NavbarUtil import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.UiUtil import com.google.android.material.appbar.MaterialToolbar -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator -import com.google.gson.Gson -import com.yausername.youtubedl_android.YoutubeDL import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt index eadbf697..f9608f10 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt @@ -8,7 +8,6 @@ import android.content.SharedPreferences import android.graphics.Canvas import android.graphics.Color import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem @@ -30,7 +29,6 @@ import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import androidx.work.WorkManager import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.DownloadItem @@ -49,7 +47,6 @@ import com.google.android.material.card.MaterialCardView import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import com.yausername.youtubedl_android.YoutubeDL import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/MoreFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/MoreFragment.kt index f6ec93bc..21504d3c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/MoreFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/MoreFragment.kt @@ -1,6 +1,5 @@ package com.deniscerri.ytdl.ui.more -import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.os.Bundle @@ -16,22 +15,14 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager -import androidx.work.WorkManager import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.R -import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.ui.more.settings.SettingsActivity import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity import com.deniscerri.ytdl.util.NavbarUtil -import com.deniscerri.ytdl.util.NotificationUtil import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.yausername.youtubedl_android.YoutubeDL -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import kotlin.system.exitProcess class MoreFragment : Fragment() { 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 409bfeeb..2fa211e8 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 @@ -195,7 +195,7 @@ class FolderSettingsFragment : BaseSettingsFragment() { }else { FileUtil.convertFileSize(cacheSize) } - clearCache!!.summary = "(${filesize}) ${resources.getString(R.string.clear_temporary_files_summary)}" + clearCache!!.summary = "${resources.getString(R.string.clear_temporary_files_summary)} (${filesize}) " clearCache!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { lifecycleScope.launch { @@ -224,7 +224,7 @@ class FolderSettingsFragment : BaseSettingsFragment() { }else { FileUtil.convertFileSize(cacheSize) } - clearCache!!.summary = "(${filesize}) ${resources.getString(R.string.clear_temporary_files_summary)}" + clearCache!!.summary = "${resources.getString(R.string.clear_temporary_files_summary)} (${filesize})" }else{ Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show() } @@ -252,7 +252,7 @@ class FolderSettingsFragment : BaseSettingsFragment() { if (list.first().state == WorkInfo.State.SUCCEEDED){ cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() } - clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}" + clearCache!!.summary = "${resources.getString(R.string.clear_temporary_files_summary)} (${FileUtil.convertFileSize(cacheSize)})" } } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt index a8b9ffd6..400b1c45 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt @@ -1,8 +1,5 @@ package com.deniscerri.ytdl.util -import android.R.attr.mimeType -import android.app.Activity -import android.content.ContentResolver import android.content.Context import android.content.Intent import android.media.MediaScannerConnection @@ -11,19 +8,12 @@ import android.os.Build import android.os.Environment import android.provider.DocumentsContract import android.provider.MediaStore -import android.util.DisplayMetrics import android.util.Log -import android.view.ViewGroup -import android.view.Window import android.webkit.MimeTypeMap -import android.widget.LinearLayout -import android.widget.TextView import android.widget.Toast import androidx.core.content.FileProvider import androidx.core.net.toUri -import androidx.core.view.isVisible import androidx.documentfile.provider.DocumentFile -import androidx.preference.Preference import androidx.preference.PreferenceManager import com.anggrayudi.storage.callback.FileCallback import com.anggrayudi.storage.callback.FolderCallback @@ -31,27 +21,20 @@ import com.anggrayudi.storage.file.copyFolderTo import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.moveFileTo import com.deniscerri.ytdl.App -import com.deniscerri.ytdl.BuildConfig -import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.R -import com.deniscerri.ytdl.util.Extensions.getMediaDuration -import com.deniscerri.ytdl.util.Extensions.toStringDuration -import com.google.android.material.bottomsheet.BottomSheetDialog import com.yausername.youtubedl_android.YoutubeDLRequest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly -import okhttp3.internal.format import okhttp3.internal.lowercase import java.io.File import java.net.URLDecoder import java.nio.charset.StandardCharsets import java.nio.file.Files -import java.nio.file.Path import java.nio.file.StandardCopyOption import java.text.DecimalFormat import java.text.DecimalFormatSymbols -import java.util.* +import java.util.Locale import kotlin.io.path.absolutePathString import kotlin.math.log10 import kotlin.math.pow diff --git a/app/src/main/java/com/deniscerri/ytdl/util/FormatUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/FormatUtil.kt index 9d974116..ea608e08 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/FormatUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/FormatUtil.kt @@ -47,7 +47,7 @@ class FormatUtil(private var context: Context) { return orderPreferences }else { - val formatImportance = mutableListOf("id","codec", "container", "language") + val formatImportance = mutableListOf("id", "language", "codec", "container") if (preferredFormatSize == "smallest") { formatImportance.add("smallsize") }else if (preferredFormatSize == "largest") { @@ -122,13 +122,27 @@ class FormatUtil(private var context: Context) { "language" -> { if (audioLanguagePreference.isBlank()) { - 0 + (b.format_note.contains("default", true)).compareTo( + a.format_note.contains( + "default", true + ) + ) + //0 } else { - (b.lang?.contains(audioLanguagePreference) == true).compareTo( + val res = (b.lang?.contains(audioLanguagePreference) == true).compareTo( a.lang?.contains( audioLanguagePreference ) == true ) + if(res == 0) { + (b.format_note.contains("default", true)).compareTo( + a.format_note.contains( + "default", true + ) + ) + }else { + res + } } } 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 15ebaeac..9575e5b2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -71,7 +71,7 @@ import com.deniscerri.ytdl.util.Extensions.createBadge import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.getMediaDuration import com.deniscerri.ytdl.util.Extensions.toStringDuration -import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeUtils import com.google.android.material.badge.ExperimentalBadgeUtils diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt index 5d95bf69..c8290fe5 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt @@ -12,7 +12,6 @@ import com.google.gson.reflect.TypeToken import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDLRequest import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.processNextEventInCurrentThread import kotlinx.coroutines.withContext import java.io.InputStreamReader import java.net.HttpURLConnection diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/YTDLPUtil.kt similarity index 98% rename from app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt rename to app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/YTDLPUtil.kt index e4dac5f2..f542957f 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/YTDLPUtil.kt @@ -1,4 +1,4 @@ -package com.deniscerri.ytdl.util.extractors +package com.deniscerri.ytdl.util.extractors.ytdlp import android.annotation.SuppressLint import android.content.Context @@ -32,8 +32,8 @@ import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FormatUtil import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDLRequest +import com.yausername.youtubedl_android.YoutubeDL import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext @@ -111,7 +111,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co if (query.contains("http")){ if (query.isYoutubeWatchVideosURL()) { request = YoutubeDLRequest(emptyList()) - val config = File(context.cacheDir.absolutePath + "/config" + System.currentTimeMillis() + "##url.txt") + val config = + File(context.cacheDir.absolutePath + "/config" + System.currentTimeMillis() + "##url.txt") config.writeText(query) request.addOption("--config", config.absolutePath) }else{ @@ -182,7 +183,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co if (playlistTitle.isNotBlank()){ playlistURL = jsonObject.getStringByAny("playlist_webpage_url").ifEmpty { query } - kotlin.runCatching { playlistIndex = jsonObject.getInt("playlist_index") } + runCatching { playlistIndex = jsonObject.getInt("playlist_index") } } val url = if (jsonObject.has("url") && results.size > 1){ jsonObject.getString("url") @@ -266,7 +267,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co } } - val res = ResultItem(0, + val res = ResultItem( + 0, url = url, title = title, author = author, @@ -417,7 +419,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co e.message?.split(System.lineSeparator())?.onEach { line -> println(line) if (line.contains("unavailable")) { - kotlin.runCatching { + runCatching { val id = Regex("""\[.*?\] (\w+):""").find(line)!!.groupValues[1] val url = urls.first { it.contains(id) } progress( @@ -464,7 +466,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co arrayOf(res.out) } val json = results[0] - val jsonArray = kotlin.runCatching { JSONArray(json) }.getOrElse { JSONArray() } + val jsonArray = runCatching { JSONArray(json) }.getOrElse { JSONArray() } return parseYTDLFormats(jsonArray) } @@ -475,19 +477,19 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co if (formatsInJSON != null) { for (f in formatsInJSON.length() - 1 downTo 0){ val format = formatsInJSON.getJSONObject(f) - kotlin.runCatching { + runCatching { if (format.get("filesize").toString() == "None") { format.remove("filesize") } } - kotlin.runCatching { + runCatching { if (format.get("filesize_approx").toString() == "None") { format.remove("filesize_approx") } } - kotlin.runCatching { + runCatching { if(format.get("format_note").toString() == "null"){ format.remove("format_note") } @@ -686,8 +688,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co val useLanguageForMetadata = sharedPreferences.getBoolean("use_app_language_for_metadata", true) if (useLanguageForMetadata) { - val lang = Locale.getDefault().language - val langTag = Locale.getDefault().toLanguageTag() + val lang = java.util.Locale.getDefault().language + val langTag = java.util.Locale.getDefault().toLanguageTag() if (context.getStringArray(R.array.subtitle_langs).contains(lang)) { extractorArgs.add("lang=$lang") }else if (context.getStringArray(R.array.subtitle_langs).contains(langTag)) { @@ -709,7 +711,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co private fun YoutubeDLRequest.addConfig(commandString: String) { this.addOption( "--config-locations", - File(context.cacheDir.absolutePath + "/${System.currentTimeMillis()}${UUID.randomUUID()}.txt").apply { + File(context.cacheDir.absolutePath + "/${System.currentTimeMillis()}${java.util.UUID.randomUUID()}.txt").apply { writeText(commandString) }.absolutePath ) @@ -956,7 +958,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co var abrSort = "" var audioQualityId : String = downloadItem.format.format_id - if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "ba", "best", "", context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){ + if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "ba", "best", "", context.getString( + R.string.worst_quality), "wa", "worst").contains(audioQualityId)){ audioQualityId = "ba/b" }else if(audioQualityId.contains("kbps_ytdlnisgeneric")){ abrSort = audioQualityId.split("kbps")[0] @@ -1134,7 +1137,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co var cont = "" val outputContainer = downloadItem.container - if(outputContainer.isNotEmpty() && outputContainer != "Default" && outputContainer != context.getString(R.string.defaultValue) && supportedContainers.contains(outputContainer)){ + if(outputContainer.isNotEmpty() && outputContainer != "Default" && outputContainer != context.getString( + R.string.defaultValue) && supportedContainers.contains(outputContainer)){ cont = outputContainer val cantRecode = listOf("avi") @@ -1225,7 +1229,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co } } }else{ - if (videoF == context.resources.getString(R.string.best_quality) || videoF == "best" || videoF == context.resources.getString(R.string.worst_quality) || videoF == "worst") { + if (videoF == context.resources.getString(R.string.best_quality) || videoF == "best" || videoF == context.resources.getString( + R.string.worst_quality) || videoF == "worst") { videoF = "bv" }else if (defaultFormats.contains(videoF)) { hasGenericResulutionFormat = videoF.split("_")[0].dropLast(1) @@ -1423,7 +1428,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co val cacheDirArg = """(--cache-dir (".*"))""".toRegex().find(downloadItem.extraCommands) if (cacheDirArg != null) { ytDlRequest.addOption("--cache-dir", cacheDirArg.groupValues.last().replace("\"", "")) - kotlin.runCatching { + runCatching { downloadItem.extraCommands = downloadItem.extraCommands.replace(cacheDirArg.value, "") } } @@ -1437,7 +1442,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co val cache = File(FileUtil.getCachePath(context)) cache.mkdirs() - val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}${UUID.randomUUID()}.txt") + val conf = + File(cache.absolutePath + "/${System.currentTimeMillis()}${UUID.randomUUID()}.txt") conf.createNewFile() conf.writeText(request.toString()) val tmp = mutableListOf() diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/webview/YTDLPWebview.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/webview/YTDLPWebview.kt new file mode 100644 index 00000000..4e2ae9ab --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/webview/YTDLPWebview.kt @@ -0,0 +1,176 @@ +package com.deniscerri.ytdl.util.extractors.ytdlp.webview + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.webkit.JavascriptInterface +import android.webkit.WebView +import android.webkit.WebViewClient +import org.json.JSONObject +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.InetAddress +import java.net.ServerSocket +import java.net.Socket +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +@SuppressLint("SetJavaScriptEnabled") +class YTDLPWebview(private val context: Context, private val port: Int, private val evalTimeoutMs: Long = 10_000L) { + private val mainHandler = Handler(Looper.getMainLooper()) + private val executor = Executors.newCachedThreadPool() + private lateinit var webView: WebView + + // map callId -> callback + private val callbacks = ConcurrentHashMap Unit>() + + init { + initWebView() + // start server off-main thread + executor.submit { startServer() } + } + + private fun initWebView() { + mainHandler.post { + webView = WebView(context) + val s = webView.settings + s.javaScriptEnabled = true + s.allowFileAccess = false + s.allowContentAccess = false + // disable access to file:// and other risky things + webView.settings.domStorageEnabled = false + webView.webViewClient = WebViewClient() + webView.addJavascriptInterface(JsBridge(), "AndroidBridge") + // Load blank page (we will eval into this context) + webView.loadData("", "text/html", "utf-8") + } + } + + inner class JsBridge { + @JavascriptInterface + fun postResponse(id: String, json: String) { + val obj = try { JSONObject(json) } catch (_: Exception) { + JSONObject().put("result", JSONObject.NULL).put("error", "invalid_json_from_js") + } + callbacks.remove(id)?.invoke(obj) + } + } + + private fun startServer() { + // Bind only to localhost + val server = ServerSocket(port, 50, InetAddress.getByName("127.0.0.1")) + while (!server.isClosed) { + val client = server.accept() + executor.submit { handleClient(client) } + } + } + + private fun handleClient(client: Socket) { + client.soTimeout = (evalTimeoutMs + 2000).toInt() + val reader = BufferedReader(InputStreamReader(client.getInputStream())) + // read until NUL terminator (wrapper should send code+'\0') + val sb = StringBuilder() + var ch = reader.read() + while (ch >= 0 && ch != 0) { + sb.append(ch.toChar()) + ch = reader.read() + } + val code = sb.toString() + + val callId = System.nanoTime().toString() + // register callback with timeout + val done = Object() + var responded = false + + callbacks[callId] = { jsonObj -> + try { + val bytes = (jsonObj.toString() + "\u0000").toByteArray() + client.getOutputStream().write(bytes) + client.getOutputStream().flush() + } catch (_: Exception) { } + finally { + try { client.close() } catch (_: Exception) {} + synchronized(done) { responded = true; done.notifyAll() } + } + } + + // schedule timeout + executor.submit { + try { + Thread.sleep(evalTimeoutMs) + } catch (_: InterruptedException) {} + if (callbacks.remove(callId) != null) { + val timeoutJson = JSONObject().put("result", JSONObject.NULL).put("logs", arrayOf()).put("error", "timeout") + try { + client.getOutputStream().write((timeoutJson.toString() + "\u0000").toByteArray()) + client.close() + } catch (_: Exception) {} + } + } + + // eval + evalJsWithConsoleCapture(code, callId) + // wait briefly for response (not strictly necessary) + synchronized(done) { + if (!responded) { + try { done.wait(evalTimeoutMs + 1000) } catch (_: InterruptedException) {} + } + } + } + + private fun evalJsWithConsoleCapture(code: String, id: String) { + // wrap code so we capture console logs and async results + val escaped = code.replace("\\", "\\\\").replace("\n", "\\n").replace("\"", "\\\"") + val wrapped = """ + (function(){ + (function(){ + var logs = []; + var origLog = console.log; + console.log = function(){ + try { + var a = Array.prototype.slice.call(arguments).map(function(x){ + try { return typeof x === 'object' ? JSON.stringify(x) : String(x); } catch (e) { return String(x); } + }); + logs.push(a.join(' ')); + } catch(e){} + try{ origLog.apply(console, arguments); }catch(e){} + }; + function send(obj){ + try{ + AndroidBridge.postResponse("$id", JSON.stringify(obj)); + }catch(e){} + } + try { + var res = (function(){ return eval("$escaped"); })(); + // if Promise-like, wait for it + if (res && typeof res.then === 'function') { + res.then(function(v){ send({result: v==undefined? null: v, logs: logs, error: null}); }) + .catch(function(err){ send({result: null, logs: logs, error: String(err)}); }); + } else { + send({result: res==undefined? null: res, logs: logs, error: null}); + } + } catch(e) { + send({result: null, logs: logs, error: String(e)}); + } + })(); + })(); + """.trimIndent() + + mainHandler.post { + try { + webView.evaluateJavascript(wrapped, null) + } catch (e: Exception) { + // immediate failure: return error + callbacks.remove(id)?.invoke(JSONObject().put("result", JSONObject.NULL).put("logs", arrayOf()).put("error", e.message)) + } + } + } + + fun shutdown() { + executor.shutdownNow() + try { executor.awaitTermination(1, TimeUnit.SECONDS) } catch (_: Exception) {} + mainHandler.post { webView.destroy() } + } +} \ No newline at end of file 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 d78a681f..f4ce9e15 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt @@ -5,7 +5,6 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.res.Configuration import android.content.res.Resources import android.os.Build @@ -33,16 +32,14 @@ import com.deniscerri.ytdl.util.Extensions.getMediaDuration import com.deniscerri.ytdl.util.Extensions.toStringDuration import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil import com.yausername.youtubedl_android.YoutubeDL import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import java.io.File diff --git a/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt index 57725142..ba412ea1 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt @@ -24,7 +24,7 @@ import com.deniscerri.ytdl.database.repository.ResultRepository import com.deniscerri.ytdl.util.Extensions.calculateNextTimeForObserving import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext