fixes, fix app preferring auto dub instead of original audio

pull/949/head
deniscerri 9 months ago
parent 1a12637397
commit 0485fb71e1
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -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"

@ -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

@ -0,0 +1,70 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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;
}

@ -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){

@ -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

@ -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

@ -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) {

@ -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

@ -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

@ -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

@ -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

@ -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() {

@ -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)})"
}
}

@ -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

@ -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
}
}
}

@ -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

@ -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

@ -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<String>()

@ -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<String, (JSONObject) -> 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("<html><body></body></html>", "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<String>()).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<String>()).put("error", e.message))
}
}
}
fun shutdown() {
executor.shutdownNow()
try { executor.awaitTermination(1, TimeUnit.SECONDS) } catch (_: Exception) {}
mainHandler.post { webView.destroy() }
}
}

@ -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

@ -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

Loading…
Cancel
Save