diff --git a/app/src/main/java/ani/dantotsu/addons/download/DownloadAddonApiV2.kt b/app/src/main/java/ani/dantotsu/addons/download/DownloadAddonApiV2.kt index b12f4beed61..a54d18861d4 100644 --- a/app/src/main/java/ani/dantotsu/addons/download/DownloadAddonApiV2.kt +++ b/app/src/main/java/ani/dantotsu/addons/download/DownloadAddonApiV2.kt @@ -9,6 +9,8 @@ interface DownloadAddonApiV2 { fun setDownloadPath(context: Context, uri: Uri): String + fun getReadPath(context: Context, uri: Uri): String + suspend fun executeFFProbe( videoUrl: String, headers: Map = emptyMap(), @@ -24,6 +26,10 @@ interface DownloadAddonApiV2 { statCallback: (Double) -> Unit ): Long + suspend fun customFFMpeg(command: String, videoUrls: List, logCallback: (String) -> Unit): Long + + suspend fun customFFProbe(command: String, videoUrls: List, logCallback: (String) -> Unit) + fun getState(sessionId: Long): String fun getStackTrace(sessionId: Long): String? diff --git a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt index eb2391ceafb..6d3a25cf0df 100644 --- a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt +++ b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt @@ -379,7 +379,7 @@ class DownloadsManager(private val context: Context) { private const val RESERVED_CHARS = "|\\?*<\":>+[]/'" fun String?.findValidName(): String { - return this?.filterNot { RESERVED_CHARS.contains(it) } ?: "" + return this?.replace("/","_")?.filterNot { RESERVED_CHARS.contains(it) } ?: "" } data class DownloadedType( diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt index ac631b90f9a..237d3b5367c 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -26,6 +26,7 @@ import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.anime.AnimeDownloaderService.AnimeDownloadTask.Companion.getTaskName +import ani.dantotsu.download.findValidName import ani.dantotsu.media.Media import ani.dantotsu.media.MediaType import ani.dantotsu.media.anime.AnimeWatchFragment @@ -224,7 +225,7 @@ class AnimeDownloaderService : Service() { task.episode ) ?: throw Exception("Failed to create output directory") - outputDir.findFile("${task.getTaskName()}.mkv")?.delete() + outputDir.findFile("${task.getTaskName().findValidName()}.mkv")?.delete() val outputFile = outputDir.createFile("video/x-matroska", "${task.getTaskName()}.mkv") ?: throw Exception("Failed to create output file") diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index 4c155b48bf6..363d233a433 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -30,11 +30,14 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.FileUrl import ani.dantotsu.R +import ani.dantotsu.addons.download.DownloadAddonManager import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager.Companion.compareName +import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.anime.AnimeDownloaderService +import ani.dantotsu.download.findValidName import ani.dantotsu.dp import ani.dantotsu.isOnline 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.PrefName 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.hasDirAccess +import com.anggrayudi.storage.file.extension import com.google.android.material.appbar.AppBarLayout import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch +import tachiyomi.core.util.lang.launchIO import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get 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().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() { override fun onReceive(context: Context, intent: Intent) { if (!this@AnimeWatchFragment::episodeAdapter.isInitialized) return diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index 27bd887a295..14be291aa1d 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -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 { if (binding.itemEpisodeDesc.maxLines == 3) binding.itemEpisodeDesc.maxLines = 100 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b7b1edab04..f699531f622 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -811,7 +811,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc Are you sure you want to purge all %1$s downloads? Failed to delete because of… %1$s - + Failed to delete Hide replies View reply View replies @@ -1017,4 +1017,6 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc Video Search Test: %1$s Image Search Test: %1$s Book Search Test: %1$s + Failed to fix + Running Fixes…