I FUCKING HATE EXOPLAYER SUBTITLES
This commit is contained in:
parent
ea96291bfc
commit
87a9df4c12
9 changed files with 257 additions and 50 deletions
|
@ -71,7 +71,17 @@ class DownloadsManager(private val context: Context) {
|
||||||
Toast.makeText(context, "Directory does not exist", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Directory does not exist", Toast.LENGTH_SHORT).show()
|
||||||
cleanDownloads()
|
cleanDownloads()
|
||||||
}
|
}
|
||||||
downloadsList.removeAll { it.title == title }
|
when (type) {
|
||||||
|
DownloadedType.Type.MANGA -> {
|
||||||
|
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA }
|
||||||
|
}
|
||||||
|
DownloadedType.Type.ANIME -> {
|
||||||
|
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME }
|
||||||
|
}
|
||||||
|
DownloadedType.Type.NOVEL -> {
|
||||||
|
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL }
|
||||||
|
}
|
||||||
|
}
|
||||||
saveDownloads()
|
saveDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +136,7 @@ class DownloadsManager(private val context: Context) {
|
||||||
{
|
{
|
||||||
val jsonString = gson.toJson(downloadsList)
|
val jsonString = gson.toJson(downloadsList)
|
||||||
val file = File(
|
val file = File(
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"Dantotsu/downloads.json"
|
"Dantotsu/downloads.json"
|
||||||
)
|
)
|
||||||
if (file.parentFile?.exists() == false) {
|
if (file.parentFile?.exists() == false) {
|
||||||
|
@ -199,7 +209,7 @@ class DownloadsManager(private val context: Context) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val destination = File(
|
val destination = File(
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"Dantotsu/${downloadedType.title}/${downloadedType.chapter}"
|
"Dantotsu/${downloadedType.title}/${downloadedType.chapter}"
|
||||||
)
|
)
|
||||||
if (directory.exists()) {
|
if (directory.exists()) {
|
||||||
|
@ -241,6 +251,46 @@ class DownloadsManager(private val context: Context) {
|
||||||
const val novelLocation = "Dantotsu/Novel"
|
const val novelLocation = "Dantotsu/Novel"
|
||||||
const val mangaLocation = "Dantotsu/Manga"
|
const val mangaLocation = "Dantotsu/Manga"
|
||||||
const val animeLocation = "Dantotsu/Anime"
|
const val animeLocation = "Dantotsu/Anime"
|
||||||
|
|
||||||
|
fun getDirectory(context: Context, type: DownloadedType.Type, title: String, chapter: String? = null): File {
|
||||||
|
return if (type == DownloadedType.Type.MANGA) {
|
||||||
|
if (chapter != null) {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"$mangaLocation/$title/$chapter"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"$mangaLocation/$title"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (type == DownloadedType.Type.ANIME) {
|
||||||
|
if (chapter != null) {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"$animeLocation/$title/$chapter"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"$animeLocation/$title"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (chapter != null) {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"$novelLocation/$title/$chapter"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"$novelLocation/$title"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import ani.dantotsu.download.video.ExoplayerDownloadService
|
||||||
import ani.dantotsu.download.video.Helper
|
import ani.dantotsu.download.video.Helper
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.SubtitleDownloader
|
||||||
import ani.dantotsu.media.anime.AnimeWatchFragment
|
import ani.dantotsu.media.anime.AnimeWatchFragment
|
||||||
import ani.dantotsu.parsers.Subtitle
|
import ani.dantotsu.parsers.Subtitle
|
||||||
import ani.dantotsu.parsers.Video
|
import ani.dantotsu.parsers.Video
|
||||||
|
@ -228,6 +229,17 @@ class AnimeDownloaderService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMediaInfo(task)
|
saveMediaInfo(task)
|
||||||
|
task.subtitle?.let {
|
||||||
|
SubtitleDownloader.downloadSubtitle(
|
||||||
|
this@AnimeDownloaderService,
|
||||||
|
it.file.url,
|
||||||
|
DownloadedType(
|
||||||
|
task.title,
|
||||||
|
task.episode,
|
||||||
|
DownloadedType.Type.ANIME,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
val downloadStarted =
|
val downloadStarted =
|
||||||
hasDownloadStarted(downloadManager, task, 30000) // 30 seconds timeout
|
hasDownloadStarted(downloadManager, task, 30000) // 30 seconds timeout
|
||||||
|
|
||||||
|
@ -313,6 +325,7 @@ class AnimeDownloaderService : Service() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("Exception while downloading file: ${e.message}")
|
logger("Exception while downloading file: ${e.message}")
|
||||||
snackString("Exception while downloading file: ${e.message}")
|
snackString("Exception while downloading file: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
broadcastDownloadFailed(task.episode)
|
broadcastDownloadFailed(task.episode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.core.content.ContextCompat.getString
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MimeTypes
|
import androidx.media3.common.MimeTypes
|
||||||
|
import androidx.media3.common.TrackSelectionParameters
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider
|
import androidx.media3.database.StandaloneDatabaseProvider
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
|
@ -30,6 +31,7 @@ import androidx.media3.exoplayer.offline.DownloadHelper
|
||||||
import androidx.media3.exoplayer.offline.DownloadManager
|
import androidx.media3.exoplayer.offline.DownloadManager
|
||||||
import androidx.media3.exoplayer.offline.DownloadService
|
import androidx.media3.exoplayer.offline.DownloadService
|
||||||
import androidx.media3.exoplayer.scheduler.Requirements
|
import androidx.media3.exoplayer.scheduler.Requirements
|
||||||
|
import androidx.media3.ui.TrackSelectionDialogBuilder
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.defaultHeaders
|
import ani.dantotsu.defaultHeaders
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
|
@ -99,28 +101,6 @@ object Helper {
|
||||||
)
|
)
|
||||||
downloadHelper.prepare(object : DownloadHelper.Callback {
|
downloadHelper.prepare(object : DownloadHelper.Callback {
|
||||||
override fun onPrepared(helper: DownloadHelper) {
|
override fun onPrepared(helper: DownloadHelper) {
|
||||||
/*TrackSelectionDialogBuilder( TODO: use this for subtitles
|
|
||||||
context, "Select Source", helper.getTracks(0).groups
|
|
||||||
) { _, overrides ->
|
|
||||||
val params = TrackSelectionParameters.Builder(context)
|
|
||||||
overrides.forEach {
|
|
||||||
params.addOverride(it.value)
|
|
||||||
}
|
|
||||||
helper.addTrackSelection(0, params.build())
|
|
||||||
ExoplayerDownloadService
|
|
||||||
DownloadService.sendAddDownload(
|
|
||||||
context,
|
|
||||||
ExoplayerDownloadService::class.java,
|
|
||||||
helper.getDownloadRequest(null),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}.apply {
|
|
||||||
setTheme(R.style.DialogTheme)
|
|
||||||
setTrackNameProvider {
|
|
||||||
if (it.frameRate > 0f) it.height.toString() + "p" else it.height.toString() + "p (fps : N/A)"
|
|
||||||
}
|
|
||||||
build().show()
|
|
||||||
}*/
|
|
||||||
helper.getDownloadRequest(null).let {
|
helper.getDownloadRequest(null).let {
|
||||||
DownloadService.sendAddDownload(
|
DownloadService.sendAddDownload(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import ani.dantotsu.download.DownloadedType
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.parsers.SubtitleType
|
import ani.dantotsu.parsers.SubtitleType
|
||||||
|
import ani.dantotsu.snackString
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class SubtitleDownloader {
|
class SubtitleDownloader {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
//doesn't really download the subtitles -\_(o_o)_/-
|
//doesn't really download the subtitles -\_(o_o)_/-
|
||||||
suspend fun downloadSubtitles(context: Context, url: String): SubtitleType =
|
suspend fun loadSubtitleType(context: Context, url: String): SubtitleType =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
||||||
val networkHelper = Injekt.get<NetworkHelper>()
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
|
@ -29,8 +33,8 @@ class SubtitleDownloader {
|
||||||
|
|
||||||
|
|
||||||
val subtitleType = when {
|
val subtitleType = when {
|
||||||
responseBody.contains("[Script Info]") == true -> SubtitleType.ASS
|
responseBody.contains("[Script Info]") -> SubtitleType.ASS
|
||||||
responseBody.contains("WEBVTT") == true -> SubtitleType.VTT
|
responseBody.contains("WEBVTT") -> SubtitleType.VTT
|
||||||
else -> SubtitleType.SRT
|
else -> SubtitleType.SRT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,5 +43,41 @@ class SubtitleDownloader {
|
||||||
return@withContext SubtitleType.UNKNOWN
|
return@withContext SubtitleType.UNKNOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//actually downloads lol
|
||||||
|
suspend fun downloadSubtitle(context: Context, url: String, downloadedType: DownloadedType) {
|
||||||
|
try {
|
||||||
|
val directory = DownloadsManager.getDirectory(context, downloadedType.type, downloadedType.title, downloadedType.chapter)
|
||||||
|
if (!directory.exists()) { //just in case
|
||||||
|
directory.mkdirs()
|
||||||
|
}
|
||||||
|
val type = loadSubtitleType(context, url)
|
||||||
|
val subtiteFile = File(directory, "subtitle.${type}")
|
||||||
|
if (subtiteFile.exists()) {
|
||||||
|
subtiteFile.delete()
|
||||||
|
}
|
||||||
|
subtiteFile.createNewFile()
|
||||||
|
|
||||||
|
val client = Injekt.get<NetworkHelper>().client
|
||||||
|
val request = Request.Builder().url(url).build()
|
||||||
|
val reponse = client.newCall(request).execute()
|
||||||
|
|
||||||
|
if (!reponse.isSuccessful) {
|
||||||
|
snackString("Failed to download subtitle")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reponse.body.byteStream().use { input ->
|
||||||
|
subtiteFile.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackString("Failed to download subtitle")
|
||||||
|
e.printStackTrace()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,9 @@ import androidx.media3.common.*
|
||||||
import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE
|
import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE
|
||||||
import androidx.media3.common.C.TRACK_TYPE_VIDEO
|
import androidx.media3.common.C.TRACK_TYPE_VIDEO
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.common.util.Util
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
|
import androidx.media3.datasource.DefaultDataSourceFactory
|
||||||
import androidx.media3.datasource.HttpDataSource
|
import androidx.media3.datasource.HttpDataSource
|
||||||
import androidx.media3.datasource.cache.CacheDataSource
|
import androidx.media3.datasource.cache.CacheDataSource
|
||||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||||
|
@ -1284,7 +1286,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
||||||
if (subtitle?.type == SubtitleType.UNKNOWN) {
|
if (subtitle?.type == SubtitleType.UNKNOWN) {
|
||||||
val context = this
|
val context = this
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val type = SubtitleDownloader.downloadSubtitles(context, subtitle!!.file.url)
|
val type = SubtitleDownloader.loadSubtitleType(context, subtitle!!.file.url)
|
||||||
val fileUri = Uri.parse(subtitle!!.file.url)
|
val fileUri = Uri.parse(subtitle!!.file.url)
|
||||||
sub = MediaItem.SubtitleConfiguration
|
sub = MediaItem.SubtitleConfiguration
|
||||||
.Builder(fileUri)
|
.Builder(fileUri)
|
||||||
|
@ -1302,8 +1304,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
||||||
}
|
}
|
||||||
println("sub: $sub")
|
println("sub: $sub")
|
||||||
} else {
|
} else {
|
||||||
|
val subUri = Uri.parse((subtitle!!.file.url))
|
||||||
sub = MediaItem.SubtitleConfiguration
|
sub = MediaItem.SubtitleConfiguration
|
||||||
.Builder(Uri.parse(subtitle!!.file.url))
|
.Builder(subUri)
|
||||||
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
|
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
|
||||||
.setMimeType(
|
.setMimeType(
|
||||||
when (subtitle?.type) {
|
when (subtitle?.type) {
|
||||||
|
@ -1338,9 +1341,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
||||||
}
|
}
|
||||||
dataSource
|
dataSource
|
||||||
}
|
}
|
||||||
|
val dafuckDataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString()))
|
||||||
cacheFactory = CacheDataSource.Factory().apply {
|
cacheFactory = CacheDataSource.Factory().apply {
|
||||||
setCache(Helper.getSimpleCache(this@ExoplayerView))
|
setCache(Helper.getSimpleCache(this@ExoplayerView))
|
||||||
setUpstreamDataSourceFactory(dataSourceFactory)
|
if (ext.server.offline) {
|
||||||
|
setUpstreamDataSourceFactory(dafuckDataSourceFactory)
|
||||||
|
} else {
|
||||||
|
setUpstreamDataSourceFactory(dataSourceFactory)
|
||||||
|
}
|
||||||
setCacheWriteDataSinkFactory(null)
|
setCacheWriteDataSinkFactory(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1374,7 +1382,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
||||||
}
|
}
|
||||||
builder.build()
|
builder.build()
|
||||||
} else {
|
} else {
|
||||||
downloadedMediaItem
|
val addedSubsDownloadedMediaItem = downloadedMediaItem.buildUpon()
|
||||||
|
if (sub != null) {
|
||||||
|
val listofnotnullsubs = immutableListOf(sub).filterNotNull()
|
||||||
|
val addLanguage = listofnotnullsubs[0].buildUpon().setLanguage("en").build()
|
||||||
|
addedSubsDownloadedMediaItem.setSubtitleConfigurations(immutableListOf(addLanguage))
|
||||||
|
episode.selectedSubtitle = 0
|
||||||
|
}
|
||||||
|
addedSubsDownloadedMediaItem.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1635,7 +1650,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > settings.watchPercentage) {
|
if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > settings.watchPercentage) {
|
||||||
preloading = true
|
preloading = true
|
||||||
nextEpisode(false) { i -> //TODO: make sure this works for offline episodes
|
nextEpisode(false) { i ->
|
||||||
val ep = episodes[episodeArr[currentEpisodeIndex + i]] ?: return@nextEpisode
|
val ep = episodes[episodeArr[currentEpisodeIndex + i]] ?: return@nextEpisode
|
||||||
val selected = media.selected ?: return@nextEpisode
|
val selected = media.selected ?: return@nextEpisode
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
@ -1806,7 +1821,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
Glide.with(this).clear(exoPlay)
|
|
||||||
finishAndRemoveTask()
|
finishAndRemoveTask()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
@ -24,6 +25,7 @@ import ani.dantotsu.download.video.Helper
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.others.Download.download
|
import ani.dantotsu.others.Download.download
|
||||||
|
import ani.dantotsu.parsers.Subtitle
|
||||||
import ani.dantotsu.parsers.VideoExtractor
|
import ani.dantotsu.parsers.VideoExtractor
|
||||||
import ani.dantotsu.parsers.VideoType
|
import ani.dantotsu.parsers.VideoType
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
@ -302,18 +304,68 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
|
val episode = media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!
|
||||||
val selectedVideo =
|
val selectedVideo =
|
||||||
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
|
if (extractor.videos.size > episode.selectedVideo) extractor.videos[episode.selectedVideo] else null
|
||||||
if (selectedVideo != null) {
|
|
||||||
Helper.startAnimeDownloadService(
|
val subtitles = extractor.subtitles
|
||||||
requireActivity(),
|
val subtitleNames = subtitles.map { it.language }
|
||||||
media!!.mainName(),
|
var subtitleToDownload: Subtitle? = null
|
||||||
episode.number,
|
if (subtitles.isNotEmpty()) {
|
||||||
selectedVideo,
|
AlertDialog.Builder(context, R.style.MyPopup)
|
||||||
null,
|
.setTitle("Download Subtitle")
|
||||||
media,
|
.setSingleChoiceItems(
|
||||||
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
subtitleNames.toTypedArray(),
|
||||||
)
|
-1
|
||||||
|
) { dialog, which ->
|
||||||
|
subtitleToDownload = subtitles[which]
|
||||||
|
}
|
||||||
|
.setPositiveButton("Download") { _, _ ->
|
||||||
|
dialog?.dismiss()
|
||||||
|
if (selectedVideo != null) {
|
||||||
|
Helper.startAnimeDownloadService(
|
||||||
|
currActivity()!!,
|
||||||
|
media!!.mainName(),
|
||||||
|
episode.number,
|
||||||
|
selectedVideo,
|
||||||
|
subtitleToDownload,
|
||||||
|
media,
|
||||||
|
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
snackString("No Video Selected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
subtitleToDownload = null
|
||||||
|
dialog.dismiss()
|
||||||
|
if (selectedVideo != null) {
|
||||||
|
Helper.startAnimeDownloadService(
|
||||||
|
currActivity()!!,
|
||||||
|
media!!.mainName(),
|
||||||
|
episode.number,
|
||||||
|
selectedVideo,
|
||||||
|
subtitleToDownload,
|
||||||
|
media,
|
||||||
|
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
snackString("No Video Selected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
snackString("No Video Selected")
|
if (selectedVideo != null) {
|
||||||
|
Helper.startAnimeDownloadService(
|
||||||
|
requireActivity(),
|
||||||
|
media!!.mainName(),
|
||||||
|
episode.number,
|
||||||
|
selectedVideo,
|
||||||
|
subtitleToDownload,
|
||||||
|
media,
|
||||||
|
episode.thumb?.url ?: media!!.banner ?: media!!.cover
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
snackString("No Video Selected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.BottomSheetDialogFragment
|
import ani.dantotsu.BottomSheetDialogFragment
|
||||||
|
@ -60,6 +62,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.media.anime.AnimeNameAdapter
|
import ani.dantotsu.media.anime.AnimeNameAdapter
|
||||||
|
import ani.dantotsu.tryWithSuspend
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
|
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
|
||||||
|
@ -11,6 +13,7 @@ import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class OfflineAnimeParser : AnimeParser() {
|
class OfflineAnimeParser : AnimeParser() {
|
||||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||||
|
@ -34,6 +37,10 @@ class OfflineAnimeParser : AnimeParser() {
|
||||||
val episodes = mutableListOf<Episode>()
|
val episodes = mutableListOf<Episode>()
|
||||||
if (directory.exists()) {
|
if (directory.exists()) {
|
||||||
directory.listFiles()?.forEach {
|
directory.listFiles()?.forEach {
|
||||||
|
//put the title and episdode number in the extra data
|
||||||
|
val extraData = mutableMapOf<String, String>()
|
||||||
|
extraData["title"] = animeLink
|
||||||
|
extraData["episode"] = it.name
|
||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
val episode = Episode(
|
val episode = Episode(
|
||||||
it.name,
|
it.name,
|
||||||
|
@ -41,6 +48,7 @@ class OfflineAnimeParser : AnimeParser() {
|
||||||
it.name,
|
it.name,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
extra = extraData,
|
||||||
sEpisode = SEpisodeImpl()
|
sEpisode = SEpisodeImpl()
|
||||||
)
|
)
|
||||||
episodes.add(episode)
|
episodes.add(episode)
|
||||||
|
@ -60,7 +68,8 @@ class OfflineAnimeParser : AnimeParser() {
|
||||||
return listOf(
|
return listOf(
|
||||||
VideoServer(
|
VideoServer(
|
||||||
episodeLink,
|
episodeLink,
|
||||||
offline = true
|
offline = true,
|
||||||
|
extraData = extra
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,6 +90,21 @@ class OfflineAnimeParser : AnimeParser() {
|
||||||
return returnList
|
return returnList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun loadByVideoServers(
|
||||||
|
episodeUrl: String,
|
||||||
|
extra: Map<String, String>?,
|
||||||
|
sEpisode: SEpisode,
|
||||||
|
callback: (VideoExtractor) -> Unit
|
||||||
|
) {
|
||||||
|
val server = loadVideoServers(episodeUrl, extra, sEpisode).first()
|
||||||
|
OfflineVideoExtractor(server).apply {
|
||||||
|
tryWithSuspend {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
callback.invoke(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getVideoExtractor(server: VideoServer): VideoExtractor {
|
override suspend fun getVideoExtractor(server: VideoServer): VideoExtractor {
|
||||||
return OfflineVideoExtractor(server)
|
return OfflineVideoExtractor(server)
|
||||||
}
|
}
|
||||||
|
@ -92,7 +116,10 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
|
||||||
get() = videoServer
|
get() = videoServer
|
||||||
|
|
||||||
override suspend fun extract(): VideoContainer {
|
override suspend fun extract(): VideoContainer {
|
||||||
val sublist = emptyList<Subtitle>()
|
val sublist = getSubtitle(
|
||||||
|
videoServer.extraData?.get("title") ?: "",
|
||||||
|
videoServer.extraData?.get("episode") ?: ""
|
||||||
|
)?: emptyList()
|
||||||
//we need to return a "fake" video so that the app doesn't crash
|
//we need to return a "fake" video so that the app doesn't crash
|
||||||
val video = Video(
|
val video = Video(
|
||||||
null,
|
null,
|
||||||
|
@ -102,4 +129,33 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
|
||||||
return VideoContainer(listOf(video), sublist)
|
return VideoContainer(listOf(video), sublist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSubtitle(title: String, episode: String): List<Subtitle>? {
|
||||||
|
currContext()?.let {
|
||||||
|
DownloadsManager.getDirectory(
|
||||||
|
it,
|
||||||
|
ani.dantotsu.download.DownloadedType.Type.ANIME,
|
||||||
|
title,
|
||||||
|
episode
|
||||||
|
).listFiles()?.forEach {
|
||||||
|
if (it.name.contains("subtitle")) {
|
||||||
|
return listOf(
|
||||||
|
Subtitle(
|
||||||
|
"Downloaded Subtitle",
|
||||||
|
Uri.fromFile(it).toString(),
|
||||||
|
determineSubtitletype(it.absolutePath)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun determineSubtitletype(url: String): SubtitleType {
|
||||||
|
return when {
|
||||||
|
url.lowercase(Locale.ROOT).endsWith("ass") -> SubtitleType.ASS
|
||||||
|
url.lowercase(Locale.ROOT).endsWith("vtt") -> SubtitleType.VTT
|
||||||
|
else -> SubtitleType.SRT
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -62,9 +62,8 @@ data class VideoServer(
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
constructor(name: String, embedUrl: String, extraData: Map<String, String>? = null)
|
constructor(name: String, embedUrl: String, extraData: Map<String, String>? = null)
|
||||||
: this(name, FileUrl(embedUrl), extraData)
|
: this(name, FileUrl(embedUrl), extraData)
|
||||||
|
constructor(name: String, offline: Boolean, extraData: Map<String, String>?)
|
||||||
constructor(name: String, offline: Boolean)
|
: this(name, FileUrl(""), extraData, null, offline)
|
||||||
: this(name, FileUrl(""), null, null, offline)
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
name: String,
|
name: String,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue