feat: video fixing options

This commit is contained in:
rebelonion 2024-05-20 11:15:11 -05:00
parent c2a10c233d
commit 10df1986e8
6 changed files with 112 additions and 3 deletions

View file

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

View file

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

View file

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

View 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

View file

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

View file

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