diff --git a/CHANGELOG.md b/CHANGELOG.md index 21cfd7fc78..6a39b440ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - `Other` - for technical stuff. ## [Unreleased] +### Added +- Added option to skip broken tracks on download ([@Secozzi](https://github.com/Secozzi)) ([#169](https://github.com/quickdesh/Animiru/pull/169)) ## [v0.19.7.7] - 2026-06-24 ### Fixed diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt index a6d74f9ac1..65ec401b50 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt @@ -50,6 +50,12 @@ object SettingsDownloadScreen : SearchableSettings { preference = downloadPreferences.downloadOnlyOverWifi, title = stringResource(MR.strings.connected_to_wifi), ), + // AM --> + Preference.PreferenceItem.SwitchPreference( + preference = downloadPreferences.ignoreBrokenTracks, + title = stringResource(AMMR.strings.pref_download_ignore_broken_tracks), + ), + // <-- AM Preference.PreferenceItem.SliderPreference( value = parallelSourceLimit, valueRange = 1..10, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 98d915aa5f..5acb6027b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -19,6 +19,8 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier import eu.kanade.tachiyomi.data.notification.NotificationHandler +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.ui.player.loader.EpisodeLoader import eu.kanade.tachiyomi.ui.player.loader.HosterLoader import eu.kanade.tachiyomi.util.storage.DiskUtil @@ -48,6 +50,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.suspendCancellableCoroutine import logcat.LogPriority +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.util.lang.launchIO @@ -64,6 +69,7 @@ import tachiyomi.i18n.animiru.AMMR import tachiyomi.i18n.aniyomi.AYMR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.io.BufferedReader import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -106,6 +112,12 @@ class Downloader( val isRunning: Boolean get() = downloaderJob?.isActive ?: false + // AM --> + val networkService: NetworkHelper by injectLazy() + val client: OkHttpClient + get() = networkService.client + // <-- AM + /** * Whether the downloader is paused */ @@ -533,7 +545,7 @@ class Downloader( "${it.first}: ${it.second}\r\n" } - val ffmpegOptions = getFFmpegOptions(video, headerOptions, ffmpegFilename()) + val ffmpegOptions = getFFmpegOptions(video, headers, headerOptions, ffmpegFilename()) val duration = getDuration(video.videoUrl, headerOptions)?.toLong() ?: 0L val logCallback = LogCallback { log -> @@ -574,7 +586,14 @@ class Downloader( } } - private fun getFFmpegOptions(video: Video, headerOptions: String, ffmpegFilename: String): Array { + private suspend fun getFFmpegOptions( + video: Video, + // AM --> + headers: Headers, + // <-- AM + headerOptions: String, + ffmpegFilename: String, + ): Array { fun formatInputs(tracks: List) = tracks.joinToString(" ", postfix = " ") { buildList { if (it.url.startsWith("http")) { @@ -593,13 +612,19 @@ class Downloader( "-metadata:s:$type:$i \"title=${track.lang}\"" }.joinToString(" ") - val subtitleInputs = formatInputs(video.subtitleTracks) - val subtitleMaps = formatMaps(video.subtitleTracks, "s") - val subtitleMetadata = formatMetadata(video.subtitleTracks, "s") + // AM --> + val subtitleTracks = filterTracks(video.subtitleTracks, headers) + // <-- AM + val subtitleInputs = formatInputs(subtitleTracks) + val subtitleMaps = formatMaps(subtitleTracks, "s") + val subtitleMetadata = formatMetadata(subtitleTracks, "s") - val audioInputs = formatInputs(video.audioTracks) - val audioMaps = formatMaps(video.audioTracks, "a", video.subtitleTracks.size) - val audioMetadata = formatMetadata(video.audioTracks, "a") + // AM --> + val audioTracks = filterTracks(video.audioTracks, headers) + // <-- AM + val audioInputs = formatInputs(audioTracks) + val audioMaps = formatMaps(audioTracks, "a", subtitleTracks.size) + val audioMetadata = formatMetadata(audioTracks, "a") val sourceStreamOptions = video.ffmpegStreamArgs.joinToString(" ") { (key, value) -> "-$key \"$value\"" @@ -630,6 +655,26 @@ class Downloader( return FFmpegKitConfig.parseArguments(command) } + // AM --> + private suspend fun filterTracks(tracks: List, headers: Headers): List { + if (!downloadPreferences.ignoreBrokenTracks.get()) return tracks + + return tracks.mapNotNull { track -> + try { + val request = Request.Builder() + .url(track.url) + .headers(headers) + .head() + .build() + client.newCall(request).awaitSuccess() + track + } catch (_: Exception) { + null + } + } + } + // <-- AM + private suspend fun getDuration(videoUrl: String, headerOptions: String): Float? { val durationFile = context.createFileInCacheDir("ffprobe_duration.txt") val durationFilePath = durationFile.toUri().toFFmpegString(context) diff --git a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt index 4fe2d8ebf1..8f0b73379c 100644 --- a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt @@ -12,6 +12,13 @@ class DownloadPreferences( true, ) + // AM --> + val ignoreBrokenTracks: Preference = preferenceStore.getBoolean( + "pref_download_ignore_broken_tracks", + false, + ) + // <-- AM + // AY --> val useExternalDownloader: Preference = preferenceStore.getBoolean("use_external_downloader", false) diff --git a/i18n-animiru/src/commonMain/moko-resources/base/strings.xml b/i18n-animiru/src/commonMain/moko-resources/base/strings.xml index c5afaf652c..d5abf294e4 100644 --- a/i18n-animiru/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-animiru/src/commonMain/moko-resources/base/strings.xml @@ -73,6 +73,9 @@ Edit info Custom entry info + + Skip broken external tracks + Storage overview Calculating storage usage... (%1$s)