fixes, fix app preferring auto dub instead of original audio

This commit is contained in:
deniscerri 2025-10-04 08:18:28 +02:00
parent 1a12637397
commit 0485fb71e1
No known key found for this signature in database
GPG key ID: 95C43D517D830350
21 changed files with 319 additions and 138 deletions

View file

@ -149,9 +149,9 @@ dependencies {
// implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer" // implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer"
implementation "io.github.junkfood02.youtubedl-android:library:0.17.4" implementation "io.github.junkfood02.youtubedl-android:library:0.18.0"
implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.17.2" implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.18.0"
implementation "io.github.junkfood02.youtubedl-android:aria2c:0.17.2" implementation "io.github.junkfood02.youtubedl-android:aria2c:0.18.0"
implementation "androidx.appcompat:appcompat:$appCompatVer" implementation "androidx.appcompat:appcompat:$appCompatVer"
implementation "androidx.constraintlayout:constraintlayout:2.2.1" implementation "androidx.constraintlayout:constraintlayout:2.2.1"

View file

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

View file

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

View file

@ -7,6 +7,7 @@ import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.ThemeUtil import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.extractors.ytdlp.webview.YTDLPWebview
import com.yausername.aria2c.Aria2c import com.yausername.aria2c.Aria2c
import com.yausername.ffmpeg.FFmpeg import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
@ -15,7 +16,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.*
class App : Application() { class App : Application() {
@ -30,8 +30,10 @@ class App : Application() {
applicationScope.launch((Dispatchers.IO)) { applicationScope.launch((Dispatchers.IO)) {
try { try {
createNotificationChannels() createNotificationChannels()
initLibraries() initLibraries()
//init js interp server
//val jsServer = YTDLPWebview(this@App, 65953)
val appVer = sharedPreferences.getString("version", "")!! val appVer = sharedPreferences.getString("version", "")!!
if(appVer.isEmpty() || appVer != BuildConfig.VERSION_NAME){ if(appVer.isEmpty() || appVer != BuildConfig.VERSION_NAME){
sharedPreferences.edit(commit = true){ sharedPreferences.edit(commit = true){

View file

@ -17,7 +17,7 @@ import com.deniscerri.ytdl.util.Extensions.isYoutubeWatchVideosURL
import com.deniscerri.ytdl.util.Extensions.needsDataUpdating import com.deniscerri.ytdl.util.Extensions.needsDataUpdating
import com.deniscerri.ytdl.util.extractors.GoogleApiUtil import com.deniscerri.ytdl.util.extractors.GoogleApiUtil
import com.deniscerri.ytdl.util.extractors.newpipe.NewPipeUtil 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 com.deniscerri.ytdl.util.extractors.YoutubeApiUtil
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -41,7 +41,7 @@ import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.FormatUtil import com.deniscerri.ytdl.util.FormatUtil
import com.deniscerri.ytdl.util.NotificationUtil 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.AlarmScheduler
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker

View file

@ -1,72 +1,11 @@
package com.deniscerri.ytdl.database.viewmodel package com.deniscerri.ytdl.database.viewmodel
import android.app.Application 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.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.DBManager
import com.deniscerri.ytdl.database.dao.CommandTemplateDao 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.DownloadItem
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil
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
class YTDLPViewModel(private val application: Application) : AndroidViewModel(application) { class YTDLPViewModel(private val application: Application) : AndroidViewModel(application) {

View file

@ -22,7 +22,7 @@ import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.UiUtil 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.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.elevation.SurfaceColors import com.google.android.material.elevation.SurfaceColors

View file

@ -59,7 +59,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.SurfaceColors import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay

View file

@ -1,7 +1,5 @@
package com.deniscerri.ytdl.ui.downloads package com.deniscerri.ytdl.ui.downloads
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -20,18 +18,14 @@ import androidx.viewpager2.widget.ViewPager2
import androidx.work.WorkManager import androidx.work.WorkManager
import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.util.Extensions.createBadge import com.deniscerri.ytdl.util.Extensions.createBadge
import com.deniscerri.ytdl.util.NavbarUtil import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.appbar.MaterialToolbar 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.TabLayout
import com.google.android.material.tabs.TabLayoutMediator 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.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -8,7 +8,6 @@ import android.content.SharedPreferences
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -30,7 +29,6 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem 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.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest

View file

@ -1,6 +1,5 @@
package com.deniscerri.ytdl.ui.more package com.deniscerri.ytdl.ui.more
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
@ -16,22 +15,14 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.WorkManager
import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity
import com.deniscerri.ytdl.util.NavbarUtil import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.system.exitProcess import kotlin.system.exitProcess
class MoreFragment : Fragment() { class MoreFragment : Fragment() {

View file

@ -195,7 +195,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
}else { }else {
FileUtil.convertFileSize(cacheSize) 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 = clearCache!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
lifecycleScope.launch { lifecycleScope.launch {
@ -224,7 +224,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
}else { }else {
FileUtil.convertFileSize(cacheSize) 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{ }else{
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show() 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){ if (list.first().state == WorkInfo.State.SUCCEEDED){
cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() } 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)})"
} }
} }

View file

@ -1,8 +1,5 @@
package com.deniscerri.ytdl.util 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.Context
import android.content.Intent import android.content.Intent
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
@ -11,19 +8,12 @@ import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.MediaStore import android.provider.MediaStore
import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.ViewGroup
import android.view.Window
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.preference.Preference
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.anggrayudi.storage.callback.FileCallback import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.callback.FolderCallback 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.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo import com.anggrayudi.storage.file.moveFileTo
import com.deniscerri.ytdl.App import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R 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 com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.internal.closeQuietly import okhttp3.internal.closeQuietly
import okhttp3.internal.format
import okhttp3.internal.lowercase import okhttp3.internal.lowercase
import java.io.File import java.io.File
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
import java.util.* import java.util.Locale
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow

View file

@ -47,7 +47,7 @@ class FormatUtil(private var context: Context) {
return orderPreferences return orderPreferences
}else { }else {
val formatImportance = mutableListOf("id","codec", "container", "language") val formatImportance = mutableListOf("id", "language", "codec", "container")
if (preferredFormatSize == "smallest") { if (preferredFormatSize == "smallest") {
formatImportance.add("smallsize") formatImportance.add("smallsize")
}else if (preferredFormatSize == "largest") { }else if (preferredFormatSize == "largest") {
@ -122,13 +122,27 @@ class FormatUtil(private var context: Context) {
"language" -> { "language" -> {
if (audioLanguagePreference.isBlank()) { if (audioLanguagePreference.isBlank()) {
0 (b.format_note.contains("default", true)).compareTo(
a.format_note.contains(
"default", true
)
)
//0
} else { } else {
(b.lang?.contains(audioLanguagePreference) == true).compareTo( val res = (b.lang?.contains(audioLanguagePreference) == true).compareTo(
a.lang?.contains( a.lang?.contains(
audioLanguagePreference audioLanguagePreference
) == true ) == true
) )
if(res == 0) {
(b.format_note.contains("default", true)).compareTo(
a.format_note.contains(
"default", true
)
)
}else {
res
}
} }
} }

View file

@ -71,7 +71,7 @@ import com.deniscerri.ytdl.util.Extensions.createBadge
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.getMediaDuration import com.deniscerri.ytdl.util.Extensions.getMediaDuration
import com.deniscerri.ytdl.util.Extensions.toStringDuration 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.BadgeDrawable
import com.google.android.material.badge.BadgeUtils import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils import com.google.android.material.badge.ExperimentalBadgeUtils

View file

@ -12,7 +12,6 @@ import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.processNextEventInCurrentThread
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.InputStreamReader import java.io.InputStreamReader
import java.net.HttpURLConnection import java.net.HttpURLConnection

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.util.extractors package com.deniscerri.ytdl.util.extractors.ytdlp
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@ -32,8 +32,8 @@ import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.FormatUtil import com.deniscerri.ytdl.util.FormatUtil
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest import com.yausername.youtubedl_android.YoutubeDLRequest
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -111,7 +111,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
if (query.contains("http")){ if (query.contains("http")){
if (query.isYoutubeWatchVideosURL()) { if (query.isYoutubeWatchVideosURL()) {
request = YoutubeDLRequest(emptyList()) 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) config.writeText(query)
request.addOption("--config", config.absolutePath) request.addOption("--config", config.absolutePath)
}else{ }else{
@ -182,7 +183,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
if (playlistTitle.isNotBlank()){ if (playlistTitle.isNotBlank()){
playlistURL = jsonObject.getStringByAny("playlist_webpage_url").ifEmpty { query } 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){ val url = if (jsonObject.has("url") && results.size > 1){
jsonObject.getString("url") 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, url = url,
title = title, title = title,
author = author, author = author,
@ -417,7 +419,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
e.message?.split(System.lineSeparator())?.onEach { line -> e.message?.split(System.lineSeparator())?.onEach { line ->
println(line) println(line)
if (line.contains("unavailable")) { if (line.contains("unavailable")) {
kotlin.runCatching { runCatching {
val id = Regex("""\[.*?\] (\w+):""").find(line)!!.groupValues[1] val id = Regex("""\[.*?\] (\w+):""").find(line)!!.groupValues[1]
val url = urls.first { it.contains(id) } val url = urls.first { it.contains(id) }
progress( progress(
@ -464,7 +466,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
arrayOf(res.out) arrayOf(res.out)
} }
val json = results[0] val json = results[0]
val jsonArray = kotlin.runCatching { JSONArray(json) }.getOrElse { JSONArray() } val jsonArray = runCatching { JSONArray(json) }.getOrElse { JSONArray() }
return parseYTDLFormats(jsonArray) return parseYTDLFormats(jsonArray)
} }
@ -475,19 +477,19 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
if (formatsInJSON != null) { if (formatsInJSON != null) {
for (f in formatsInJSON.length() - 1 downTo 0){ for (f in formatsInJSON.length() - 1 downTo 0){
val format = formatsInJSON.getJSONObject(f) val format = formatsInJSON.getJSONObject(f)
kotlin.runCatching { runCatching {
if (format.get("filesize").toString() == "None") { if (format.get("filesize").toString() == "None") {
format.remove("filesize") format.remove("filesize")
} }
} }
kotlin.runCatching { runCatching {
if (format.get("filesize_approx").toString() == "None") { if (format.get("filesize_approx").toString() == "None") {
format.remove("filesize_approx") format.remove("filesize_approx")
} }
} }
kotlin.runCatching { runCatching {
if(format.get("format_note").toString() == "null"){ if(format.get("format_note").toString() == "null"){
format.remove("format_note") 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) val useLanguageForMetadata = sharedPreferences.getBoolean("use_app_language_for_metadata", true)
if (useLanguageForMetadata) { if (useLanguageForMetadata) {
val lang = Locale.getDefault().language val lang = java.util.Locale.getDefault().language
val langTag = Locale.getDefault().toLanguageTag() val langTag = java.util.Locale.getDefault().toLanguageTag()
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) { if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs.add("lang=$lang") extractorArgs.add("lang=$lang")
}else if (context.getStringArray(R.array.subtitle_langs).contains(langTag)) { }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) { private fun YoutubeDLRequest.addConfig(commandString: String) {
this.addOption( this.addOption(
"--config-locations", "--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) writeText(commandString)
}.absolutePath }.absolutePath
) )
@ -956,7 +958,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
var abrSort = "" var abrSort = ""
var audioQualityId : String = downloadItem.format.format_id 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" audioQualityId = "ba/b"
}else if(audioQualityId.contains("kbps_ytdlnisgeneric")){ }else if(audioQualityId.contains("kbps_ytdlnisgeneric")){
abrSort = audioQualityId.split("kbps")[0] abrSort = audioQualityId.split("kbps")[0]
@ -1134,7 +1137,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
var cont = "" var cont = ""
val outputContainer = downloadItem.container 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 cont = outputContainer
val cantRecode = listOf("avi") val cantRecode = listOf("avi")
@ -1225,7 +1229,8 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
} }
} }
}else{ }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" videoF = "bv"
}else if (defaultFormats.contains(videoF)) { }else if (defaultFormats.contains(videoF)) {
hasGenericResulutionFormat = videoF.split("_")[0].dropLast(1) 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) val cacheDirArg = """(--cache-dir (".*"))""".toRegex().find(downloadItem.extraCommands)
if (cacheDirArg != null) { if (cacheDirArg != null) {
ytDlRequest.addOption("--cache-dir", cacheDirArg.groupValues.last().replace("\"", "")) ytDlRequest.addOption("--cache-dir", cacheDirArg.groupValues.last().replace("\"", ""))
kotlin.runCatching { runCatching {
downloadItem.extraCommands = downloadItem.extraCommands.replace(cacheDirArg.value, "") 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)) val cache = File(FileUtil.getCachePath(context))
cache.mkdirs() 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.createNewFile()
conf.writeText(request.toString()) conf.writeText(request.toString())
val tmp = mutableListOf<String>() val tmp = mutableListOf<String>()

View file

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

View file

@ -5,7 +5,6 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build 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.Extensions.toStringDuration
import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil 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 com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import java.io.File import java.io.File

View file

@ -24,7 +24,7 @@ import com.deniscerri.ytdl.database.repository.ResultRepository
import com.deniscerri.ytdl.util.Extensions.calculateNextTimeForObserving import com.deniscerri.ytdl.util.Extensions.calculateNextTimeForObserving
import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil 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 com.google.gson.Gson
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext