feat: video fixing options
This commit is contained in:
parent
c2a10c233d
commit
10df1986e8
6 changed files with 112 additions and 3 deletions
|
@ -9,6 +9,8 @@ interface DownloadAddonApiV2 {
|
||||||
|
|
||||||
fun setDownloadPath(context: Context, uri: Uri): String
|
fun setDownloadPath(context: Context, uri: Uri): String
|
||||||
|
|
||||||
|
fun getReadPath(context: Context, uri: Uri): String
|
||||||
|
|
||||||
suspend fun executeFFProbe(
|
suspend fun executeFFProbe(
|
||||||
videoUrl: String,
|
videoUrl: String,
|
||||||
headers: Map<String, String> = emptyMap(),
|
headers: Map<String, String> = emptyMap(),
|
||||||
|
@ -24,6 +26,10 @@ interface DownloadAddonApiV2 {
|
||||||
statCallback: (Double) -> Unit
|
statCallback: (Double) -> Unit
|
||||||
): Long
|
): Long
|
||||||
|
|
||||||
|
suspend fun customFFMpeg(command: String, videoUrls: List<String>, logCallback: (String) -> Unit): Long
|
||||||
|
|
||||||
|
suspend fun customFFProbe(command: String, videoUrls: List<String>, logCallback: (String) -> Unit)
|
||||||
|
|
||||||
fun getState(sessionId: Long): String
|
fun getState(sessionId: Long): String
|
||||||
|
|
||||||
fun getStackTrace(sessionId: Long): String?
|
fun getStackTrace(sessionId: Long): String?
|
||||||
|
|
|
@ -379,7 +379,7 @@ class DownloadsManager(private val context: Context) {
|
||||||
|
|
||||||
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
|
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
|
||||||
fun String?.findValidName(): String {
|
fun String?.findValidName(): String {
|
||||||
return this?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
|
return this?.replace("/","_")?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DownloadedType(
|
data class DownloadedType(
|
||||||
|
|
|
@ -26,6 +26,7 @@ import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
|
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
|
||||||
import ani.dantotsu.download.anime.AnimeDownloaderService.AnimeDownloadTask.Companion.getTaskName
|
import ani.dantotsu.download.anime.AnimeDownloaderService.AnimeDownloadTask.Companion.getTaskName
|
||||||
|
import ani.dantotsu.download.findValidName
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaType
|
import ani.dantotsu.media.MediaType
|
||||||
import ani.dantotsu.media.anime.AnimeWatchFragment
|
import ani.dantotsu.media.anime.AnimeWatchFragment
|
||||||
|
@ -224,7 +225,7 @@ class AnimeDownloaderService : Service() {
|
||||||
task.episode
|
task.episode
|
||||||
) ?: throw Exception("Failed to create output directory")
|
) ?: throw Exception("Failed to create output directory")
|
||||||
|
|
||||||
outputDir.findFile("${task.getTaskName()}.mkv")?.delete()
|
outputDir.findFile("${task.getTaskName().findValidName()}.mkv")?.delete()
|
||||||
val outputFile =
|
val outputFile =
|
||||||
outputDir.createFile("video/x-matroska", "${task.getTaskName()}.mkv")
|
outputDir.createFile("video/x-matroska", "${task.getTaskName()}.mkv")
|
||||||
?: throw Exception("Failed to create output file")
|
?: throw Exception("Failed to create output file")
|
||||||
|
|
|
@ -30,11 +30,14 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.addons.download.DownloadAddonManager
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.download.DownloadsManager.Companion.compareName
|
import ani.dantotsu.download.DownloadsManager.Companion.compareName
|
||||||
|
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
|
||||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||||
|
import ani.dantotsu.download.findValidName
|
||||||
import ani.dantotsu.dp
|
import ani.dantotsu.dp
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
@ -54,15 +57,20 @@ import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.toast
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog
|
import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog
|
||||||
import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess
|
import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess
|
||||||
|
import com.anggrayudi.storage.file.extension
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import tachiyomi.core.util.lang.launchIO
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
@ -492,6 +500,88 @@ class AnimeWatchFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@kotlin.OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun fixDownload(i: String) {
|
||||||
|
toast(R.string.running_fixes)
|
||||||
|
launchIO {
|
||||||
|
try {
|
||||||
|
val context = context ?: throw Exception("Context is null")
|
||||||
|
val directory =
|
||||||
|
getSubDirectory(context, MediaType.ANIME, false, media.mainName(), i)
|
||||||
|
?: throw Exception("Directory is null")
|
||||||
|
val files = directory.listFiles()
|
||||||
|
val videoFiles = files.filter { it.extension == "mp4" || it.extension == "mkv" }
|
||||||
|
if (videoFiles.size != 1) {
|
||||||
|
val biggest =
|
||||||
|
videoFiles.filter { it.length() > 1000 }.maxByOrNull { it.length() }
|
||||||
|
?: throw Exception("No video files found")
|
||||||
|
val newName =
|
||||||
|
AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
|
||||||
|
.findValidName() + "." + biggest.extension
|
||||||
|
videoFiles.forEach {
|
||||||
|
if (it != biggest) {
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newName != biggest.name) {
|
||||||
|
biggest.renameTo(newName)
|
||||||
|
}
|
||||||
|
toast(context.getString(R.string.success) + " (1)")
|
||||||
|
} else {
|
||||||
|
val tempFile =
|
||||||
|
directory.createFile("video/x-matroska", "temp.mkv")
|
||||||
|
?: throw Exception("Temp file is null")
|
||||||
|
val ffExtension = Injekt.get<DownloadAddonManager>().extension?.extension!!
|
||||||
|
val tempPath = ffExtension.setDownloadPath(
|
||||||
|
context,
|
||||||
|
tempFile.uri
|
||||||
|
)
|
||||||
|
val videoPath = ffExtension.getReadPath(
|
||||||
|
context,
|
||||||
|
videoFiles[0].uri
|
||||||
|
)
|
||||||
|
|
||||||
|
val id = ffExtension.customFFMpeg(
|
||||||
|
"1", listOf(videoPath, tempPath)
|
||||||
|
) { log ->
|
||||||
|
Logger.log(log)
|
||||||
|
}
|
||||||
|
val timeOut = System.currentTimeMillis() + 1000 * 60 * 10
|
||||||
|
while (ffExtension.getState(id) != "COMPLETED") {
|
||||||
|
if (ffExtension.getState(id) == "FAILED") {
|
||||||
|
Logger.log("Failed to fix download")
|
||||||
|
ffExtension.getStackTrace(id)?.let {
|
||||||
|
Logger.log(it)
|
||||||
|
}
|
||||||
|
toast(R.string.failed_to_fix)
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
if (System.currentTimeMillis() > timeOut) {
|
||||||
|
Logger.log("Failed to fix download: Timeout")
|
||||||
|
toast(R.string.failed_to_fix)
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ffExtension.hadError(id)) {
|
||||||
|
Logger.log("Failed to fix download: ${ffExtension.getStackTrace(id)}")
|
||||||
|
toast(R.string.failed_to_fix)
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
val name = videoFiles[0].name
|
||||||
|
if (videoFiles[0].delete().not()) {
|
||||||
|
toast(R.string.delete_fail)
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
tempFile.renameTo(name!!)
|
||||||
|
toast(context.getString(R.string.success) + " (2)")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast(getString(R.string.error_msg, e.message))
|
||||||
|
Logger.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (!this@AnimeWatchFragment::episodeAdapter.isInitialized) return
|
if (!this@AnimeWatchFragment::episodeAdapter.isInitialized) return
|
||||||
|
|
|
@ -334,6 +334,16 @@ class EpisodeAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.itemDownload.setOnLongClickListener {
|
||||||
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
|
||||||
|
val episodeNumber = arr[bindingAdapterPosition].number
|
||||||
|
if (downloadedEpisodes.contains(episodeNumber)) {
|
||||||
|
fragment.fixDownload(episodeNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
binding.itemEpisodeDesc.setOnClickListener {
|
binding.itemEpisodeDesc.setOnClickListener {
|
||||||
if (binding.itemEpisodeDesc.maxLines == 3)
|
if (binding.itemEpisodeDesc.maxLines == 3)
|
||||||
binding.itemEpisodeDesc.maxLines = 100
|
binding.itemEpisodeDesc.maxLines = 100
|
||||||
|
|
|
@ -811,7 +811,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="purge_confirm">Are you sure you want to purge all %1$s downloads?</string>
|
<string name="purge_confirm">Are you sure you want to purge all %1$s downloads?</string>
|
||||||
|
|
||||||
<string name="delete_fail_reason">Failed to delete because of… %1$s</string>
|
<string name="delete_fail_reason">Failed to delete because of… %1$s</string>
|
||||||
|
<string name="delete_fail">Failed to delete</string>
|
||||||
<string name="hide_replies">Hide replies</string>
|
<string name="hide_replies">Hide replies</string>
|
||||||
<string name="view_reply">View reply</string>
|
<string name="view_reply">View reply</string>
|
||||||
<string name="view_replies">View replies</string>
|
<string name="view_replies">View replies</string>
|
||||||
|
@ -1017,4 +1017,6 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="video_search_test">Video Search Test: %1$s</string>
|
<string name="video_search_test">Video Search Test: %1$s</string>
|
||||||
<string name="image_search_test">Image Search Test: %1$s</string>
|
<string name="image_search_test">Image Search Test: %1$s</string>
|
||||||
<string name="book_search_test">Book Search Test: %1$s</string>
|
<string name="book_search_test">Book Search Test: %1$s</string>
|
||||||
|
<string name="failed_to_fix">Failed to fix</string>
|
||||||
|
<string name="running_fixes">Running Fixes…</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue