Skip to content

Commit

Permalink
feat: support for multiple audio/subtitle downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
rebelonion committed May 16, 2024
1 parent fd8dd26 commit f1d16ba
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 113 deletions.
2 changes: 1 addition & 1 deletion app/src/main/java/ani/dantotsu/Network.kt
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ suspend fun <T> tryWithSuspend(
* **/
data class FileUrl(
var url: String,
val headers: Map<String, String> = mapOf()
var headers: Map<String, String> = mapOf()
) : Serializable {
companion object {
operator fun get(url: String?, headers: Map<String, String> = mapOf()): FileUrl? {
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/ani/dantotsu/addons/AddonLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.pm.PackageInfoCompat
import ani.dantotsu.addons.download.DownloadAddon
import ani.dantotsu.addons.download.DownloadAddonApi
import ani.dantotsu.addons.download.DownloadAddonApiV2
import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.addons.download.DownloadLoadResult
import ani.dantotsu.addons.torrent.TorrentAddon
Expand Down Expand Up @@ -101,8 +101,8 @@ class AddonLoader {
}

AddonType.DOWNLOAD -> {
val extension = instance as? DownloadAddonApi
?: throw IllegalStateException("Extension is not a DownloadAddonApi")
val extension = instance as? DownloadAddonApiV2
?: throw IllegalStateException("Extension is not a DownloadAddonApiV2")
DownloadLoadResult.Success(
DownloadAddon.Installed(
name = extName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ sealed class DownloadAddon : Addon() {
override val pkgName: String,
override val versionName: String,
override val versionCode: Long,
val extension: DownloadAddonApi,
val extension: DownloadAddonApiV2,
val icon: Drawable?,
val hasUpdate: Boolean = false,
) : Addon.Installed(name, pkgName, versionName, versionCode)
Expand Down
21 changes: 0 additions & 21 deletions app/src/main/java/ani/dantotsu/addons/download/DownloadAddonApi.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ani.dantotsu.addons.download

import android.content.Context
import android.net.Uri

interface DownloadAddonApiV2 {

fun cancelDownload(sessionId: Long)

fun setDownloadPath(context: Context, uri: Uri): String

suspend fun executeFFProbe(
videoUrl: String,
headers: Map<String, String> = emptyMap(),
logCallback: (String) -> Unit
)

suspend fun executeFFMpeg(
videoUrl: String,
downloadPath: String,
headers: Map<String, String> = emptyMap(),
subtitleUrls: List<Pair<String, String>> = emptyList(),
audioUrls: List<Pair<String, String>> = emptyList(),
statCallback: (Double) -> Unit
): Long

fun getState(sessionId: Long): String

fun getStackTrace(sessionId: Long): String?

fun hadError(sessionId: Long): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
import ani.dantotsu.download.anime.AnimeDownloaderService.AnimeDownloadTask.Companion.getTaskName
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.SubtitleDownloader
import ani.dantotsu.media.anime.AnimeWatchFragment
import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.Video
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.snackString
Expand Down Expand Up @@ -227,42 +225,40 @@ class AnimeDownloaderService : Service() {
) ?: throw Exception("Failed to create output directory")

outputDir.findFile("${task.getTaskName()}.mkv")?.delete()
val outputFile = outputDir.createFile("video/x-matroska", "${task.getTaskName()}.mkv")
?: throw Exception("Failed to create output file")
val outputFile =
outputDir.createFile("video/x-matroska", "${task.getTaskName()}.mkv")
?: throw Exception("Failed to create output file")

var percent = 0
var totalLength = 0.0
val path = ffExtension!!.setDownloadPath(
this@AnimeDownloaderService,
outputFile.uri
)
val headersStringBuilder = StringBuilder()
task.video.file.headers.forEach {
headersStringBuilder.append("\"${it.key}: ${it.value}\"\'\r\n\'")
}
if (!task.video.file.headers.containsKey("User-Agent")) { //headers should never be empty now
headersStringBuilder.append("\"").append("User-Agent: ")
.append(defaultHeaders["User-Agent"]).append("\"\'\r\n\'")
if (!task.video.file.headers.containsKey("User-Agent")
&& !task.video.file.headers.containsKey("user-agent")
) {
val newHeaders = task.video.file.headers.toMutableMap()
newHeaders["User-Agent"] = defaultHeaders["User-Agent"]!!
task.video.file.headers = newHeaders
}
val probeRequest =
"-headers $headersStringBuilder -i \"${task.video.file.url}\" -show_entries format=duration -v quiet -of csv=\"p=0\""

ffExtension.executeFFProbe(
probeRequest
task.video.file.url,
task.video.file.headers
) {
if (it.toDoubleOrNull() != null) {
totalLength = it.toDouble()
}
}

val headers = headersStringBuilder.toString()
var request = "-headers $headers "
request += "-i \"${task.video.file.url}\" -c copy -map 0:v -map 0:a -map 0:s?" +
" -f matroska -timeout 600 -reconnect 1" +
" -reconnect_streamed 1 -allowed_extensions ALL " +
"-tls_verify 0 $path -v trace"
Logger.log("Request: $request")
val ffTask =
ffExtension.executeFFMpeg(request) {
ffExtension.executeFFMpeg(
task.video.file.url,
path,
task.video.file.headers,
task.subtitle,
task.audio,
) {
// CALLED WHEN SESSION GENERATES STATISTICS
val timeInMilliseconds = it
if (timeInMilliseconds > 0 && totalLength > 0) {
Expand All @@ -275,17 +271,6 @@ class AnimeDownloaderService : Service() {
ffTask

saveMediaInfo(task)
task.subtitle?.let {
SubtitleDownloader.downloadSubtitle(
this@AnimeDownloaderService,
it.file.url,
DownloadedType(
task.title,
task.episode,
MediaType.ANIME,
)
)
}

// periodically check if the download is complete
while (ffExtension.getState(ffTask) != "COMPLETED") {
Expand Down Expand Up @@ -559,7 +544,8 @@ class AnimeDownloaderService : Service() {
val title: String,
val episode: String,
val video: Video,
val subtitle: Subtitle? = null,
val subtitle: List<Pair<String, String>> = emptyList(),
val audio: List<Pair<String, String>> = emptyList(),
val sourceMedia: Media? = null,
val episodeImage: String? = null,
val retries: Int = 2,
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/ani/dantotsu/download/video/Helper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ object Helper {
title: String,
episode: String,
video: Video,
subtitle: Subtitle? = null,
subtitle: List<Pair<String, String>> = emptyList(),
audio: List<Pair<String, String>> = emptyList(),
sourceMedia: Media? = null,
episodeImage: String? = null
) {
Expand All @@ -66,6 +67,7 @@ object Helper {
episode,
video,
subtitle,
audio,
sourceMedia,
episodeImage
)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class SubtitleDownloader {
}

//actually downloads lol
@Deprecated("handled externally")
suspend fun downloadSubtitle(
context: Context,
url: String,
Expand Down
121 changes: 70 additions & 51 deletions app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,8 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
val selectedVideo =
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
val subtitleNames = subtitles.map { it.language }
var subtitleToDownload: Subtitle? = null
var selectedSubtitles: MutableList<Pair<String, String>> = mutableListOf()
var selectedAudioTracks: MutableList<Pair<String, String>> = mutableListOf()
val activity = currActivity() ?: requireActivity()
selectedVideo?.file?.url?.let { url ->
if (url.startsWith("magnet:") || url.endsWith(".torrent")) {
Expand Down Expand Up @@ -552,72 +553,90 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
}
}
}
val currContext = currContext() ?: requireContext()
fun go() {
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
activity,
media!!.mainName(),
episode.number,
selectedVideo,
selectedSubtitles,
selectedAudioTracks,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
broadcastDownloadStarted(episode.number, activity)
} else {
snackString(R.string.no_video_selected)
}
}
fun checkAudioTracks() {
val audioTracks = extractor.audioTracks.map { it.lang }
if (audioTracks.isNotEmpty()) {
val audioNamesArray = audioTracks.toTypedArray()
val checkedItems = BooleanArray(audioNamesArray.size) { false }
val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup)
.setTitle(R.string.download_audio_tracks)
.setMultiChoiceItems(audioNamesArray, checkedItems) { _, which, isChecked ->
val audioPair = Pair(extractor.audioTracks[which].url, extractor.audioTracks[which].lang)
if (isChecked) {
selectedAudioTracks.add(audioPair)
} else {
selectedAudioTracks.remove(audioPair)
}
}
.setPositiveButton(R.string.download) { _, _ ->
dialog?.dismiss()
go()
}
.setNegativeButton(R.string.skip) { dialog, _ ->
selectedAudioTracks = mutableListOf()
go()
dialog.dismiss()
}
.setNeutralButton(R.string.cancel) { dialog, _ ->
selectedAudioTracks = mutableListOf()
dialog.dismiss()
}
.show()
alertDialog.window?.setDimAmount(0.8f)
} else {
go()
}
}
if (subtitles.isNotEmpty()) {
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
val subtitleNamesArray = subtitleNames.toTypedArray()
val checkedItems = BooleanArray(subtitleNamesArray.size) { false }

val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup)
.setTitle(R.string.download_subtitle)
.setSingleChoiceItems(
subtitleNames.toTypedArray(),
-1
) { _, which ->
subtitleToDownload = subtitles[which]
.setMultiChoiceItems(subtitleNamesArray, checkedItems) { _, which, isChecked ->
val subtitlePair = Pair(subtitles[which].file.url, subtitles[which].language)
if (isChecked) {
selectedSubtitles.add(subtitlePair)
} else {
selectedSubtitles.remove(subtitlePair)
}
}
.setPositiveButton(R.string.download) { _, _ ->
dialog?.dismiss()
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
activity,
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
broadcastDownloadStarted(episode.number, activity)
} else {
snackString(R.string.no_video_selected)
}
checkAudioTracks()
}
.setNegativeButton(R.string.skip) { dialog, _ ->
subtitleToDownload = null
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
currActivity()!!,
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
broadcastDownloadStarted(episode.number, activity)
} else {
snackString(R.string.no_video_selected)
}
selectedSubtitles = mutableListOf()
checkAudioTracks()
dialog.dismiss()
}
.setNeutralButton(R.string.cancel) { dialog, _ ->
subtitleToDownload = null
selectedSubtitles = mutableListOf()
dialog.dismiss()
}
.show()
alertDialog.window?.setDimAmount(0.8f)

} else {
if (selectedVideo != null) {
Helper.startAnimeDownloadService(
requireActivity(),
media!!.mainName(),
episode.number,
selectedVideo,
subtitleToDownload,
media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover
)
broadcastDownloadStarted(episode.number, activity)
} else {
snackString(R.string.no_video_selected)
}
checkAudioTracks()
}
}
dismiss()
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
<string name="would_you_like_to_install">Would you like to install it?</string>
<string name="torrent_addon_not_available">Torrent Add-on not available</string>
<string name="download_subtitle">Download Subtitle</string>
<string name="download_audio_tracks">Download Audio Tracks</string>
<string name="no_video_selected">No video selected</string>
<string name="no_subtitles_available">No subtitles available</string>
<string name="vote_out_of_total">(%1$s out of %2$s liked this review)</string>
Expand Down

0 comments on commit f1d16ba

Please sign in to comment.