forked from recloudstream/cloudstream
added download subtitles option and fixed subtitle download
This commit is contained in:
parent
d5a4a65ced
commit
10c945f497
6 changed files with 257 additions and 139 deletions
|
@ -71,6 +71,9 @@ class DownloadFileGenerator(
|
|||
.removeSuffix(".vtt")
|
||||
.removeSuffix(".srt")
|
||||
.removeSuffix(".txt")
|
||||
.trim()
|
||||
.removePrefix("(")
|
||||
.removeSuffix(")")
|
||||
|
||||
subtitleCallback(
|
||||
SubtitleData(
|
||||
|
|
|
@ -48,6 +48,9 @@ const val ACTION_SHOW_OPTIONS = 10
|
|||
const val ACTION_CLICK_DEFAULT = 11
|
||||
const val ACTION_SHOW_TOAST = 12
|
||||
|
||||
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13
|
||||
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
|
||||
|
||||
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
||||
|
||||
class EpisodeAdapter(
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
|
@ -43,7 +44,6 @@ import com.lagradost.cloudstream3.*
|
|||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||
import com.lagradost.cloudstream3.APIHolder.getId
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
|
@ -70,6 +70,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getFolderName
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
|
@ -88,6 +89,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
|
||||
import kotlinx.android.synthetic.main.fragment_result.*
|
||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||
|
@ -205,7 +207,80 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
|
||||
private var updateUIListener: (() -> Unit)? = null
|
||||
|
||||
suspend fun startDownload(
|
||||
private fun downloadSubtitle(
|
||||
context: Context?,
|
||||
link: SubtitleData,
|
||||
meta: VideoDownloadManager.DownloadEpisodeMetadata,
|
||||
) {
|
||||
context?.let { ctx ->
|
||||
val fileName = getFileName(ctx, meta)
|
||||
val folder = getFolder(meta.type ?: return, meta.mainName)
|
||||
downloadSubtitle(
|
||||
ctx,
|
||||
ExtractorSubtitleLink(link.name, link.url, ""),
|
||||
fileName,
|
||||
folder
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadSubtitle(
|
||||
context: Context?,
|
||||
link: ExtractorSubtitleLink,
|
||||
fileName: String,
|
||||
folder: String
|
||||
) {
|
||||
ioSafe {
|
||||
VideoDownloadManager.downloadThing(
|
||||
context ?: return@ioSafe,
|
||||
link,
|
||||
"$fileName ${link.name}",
|
||||
folder,
|
||||
if (link.url.contains(".srt")) ".srt" else "vtt",
|
||||
false,
|
||||
null
|
||||
) {
|
||||
// no notification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMeta(
|
||||
episode: ResultEpisode,
|
||||
titleName: String,
|
||||
apiName: String,
|
||||
currentPoster: String,
|
||||
currentIsMovie: Boolean,
|
||||
tvType: TvType,
|
||||
): VideoDownloadManager.DownloadEpisodeMetadata {
|
||||
return VideoDownloadManager.DownloadEpisodeMetadata(
|
||||
episode.id,
|
||||
sanitizeFilename(titleName),
|
||||
apiName,
|
||||
episode.poster ?: currentPoster,
|
||||
episode.name,
|
||||
if (currentIsMovie) null else episode.season,
|
||||
if (currentIsMovie) null else episode.episode,
|
||||
tvType,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFolder(currentType: TvType, titleName: String): String {
|
||||
return when (currentType) {
|
||||
TvType.Anime -> "Anime/$titleName"
|
||||
TvType.Movie -> "Movies"
|
||||
TvType.AnimeMovie -> "Movies"
|
||||
TvType.TvSeries -> "TVSeries/$titleName"
|
||||
TvType.OVA -> "OVA"
|
||||
TvType.Cartoon -> "Cartoons/$titleName"
|
||||
TvType.Torrent -> "Torrent"
|
||||
TvType.Documentary -> "Documentaries"
|
||||
TvType.AsianDrama -> "AsianDrama"
|
||||
}
|
||||
}
|
||||
|
||||
fun startDownload(
|
||||
context: Context?,
|
||||
episode: ResultEpisode,
|
||||
currentIsMovie: Boolean,
|
||||
currentHeaderName: String,
|
||||
|
@ -217,110 +292,87 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
links: List<ExtractorLink>,
|
||||
subs: List<SubtitleData>?
|
||||
) {
|
||||
val titleName = sanitizeFilename(currentHeaderName)
|
||||
try {
|
||||
if (context == null) return
|
||||
|
||||
val meta = VideoDownloadManager.DownloadEpisodeMetadata(
|
||||
episode.id,
|
||||
titleName,
|
||||
apiName,
|
||||
episode.poster ?: currentPoster,
|
||||
episode.name,
|
||||
if (currentIsMovie) null else episode.season,
|
||||
if (currentIsMovie) null else episode.episode
|
||||
)
|
||||
|
||||
val folder = when (currentType) {
|
||||
TvType.Anime -> "Anime/$titleName"
|
||||
TvType.Movie -> "Movies"
|
||||
TvType.AnimeMovie -> "Movies"
|
||||
TvType.TvSeries -> "TVSeries/$titleName"
|
||||
TvType.OVA -> "OVA"
|
||||
TvType.Cartoon -> "Cartoons/$titleName"
|
||||
TvType.Torrent -> "Torrent"
|
||||
TvType.Documentary -> "Documentaries"
|
||||
TvType.AsianDrama -> "AsianDrama"
|
||||
}
|
||||
|
||||
val src = "$DOWNLOAD_NAVIGATE_TO/$parentId" // url ?: return@let
|
||||
|
||||
// SET VISUAL KEYS
|
||||
setKey(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
parentId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
apiName,
|
||||
url,
|
||||
currentType,
|
||||
currentHeaderName,
|
||||
currentPoster,
|
||||
parentId,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
|
||||
setKey(
|
||||
getFolderName(
|
||||
DOWNLOAD_EPISODE_CACHE,
|
||||
parentId.toString()
|
||||
), // 3 deep folder for faster acess
|
||||
episode.id.toString(),
|
||||
VideoDownloadHelper.DownloadEpisodeCached(
|
||||
episode.name,
|
||||
episode.poster,
|
||||
episode.episode,
|
||||
episode.season,
|
||||
episode.id,
|
||||
parentId,
|
||||
episode.rating,
|
||||
episode.description,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
|
||||
// DOWNLOAD VIDEO
|
||||
VideoDownloadManager.downloadEpisodeUsingWorker(
|
||||
context ?: return,
|
||||
src,//url ?: return,
|
||||
folder,
|
||||
meta,
|
||||
links
|
||||
)
|
||||
// 1. Checks if the lang should be downloaded
|
||||
// 2. Makes it into the download format
|
||||
// 3. Downloads it as a .vtt file
|
||||
val downloadList = getDownloadSubsLanguageISO639_1()
|
||||
subs?.let { subsList ->
|
||||
subsList.filter {
|
||||
downloadList.contains(
|
||||
SubtitleHelper.fromLanguageToTwoLetters(
|
||||
it.name,
|
||||
true
|
||||
)
|
||||
val meta =
|
||||
getMeta(
|
||||
episode,
|
||||
currentHeaderName,
|
||||
apiName,
|
||||
currentPoster,
|
||||
currentIsMovie,
|
||||
currentType
|
||||
)
|
||||
}
|
||||
.map { ExtractorSubtitleLink(it.name, it.url, "") }
|
||||
.forEach { link ->
|
||||
val epName = meta.name
|
||||
?: "${context?.getString(R.string.episode)} ${meta.episode}"
|
||||
val fileName =
|
||||
sanitizeFilename(epName + if (downloadList.size > 1) " ${link.name}" else "")
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
normalSafeApiCall {
|
||||
VideoDownloadManager.downloadThing(
|
||||
context ?: return@normalSafeApiCall,
|
||||
link,
|
||||
fileName,
|
||||
folder,
|
||||
"vtt",
|
||||
false,
|
||||
null
|
||||
) {
|
||||
// no notification
|
||||
}
|
||||
}
|
||||
}
|
||||
val folder = getFolder(currentType, currentHeaderName)
|
||||
|
||||
val src = "$DOWNLOAD_NAVIGATE_TO/$parentId" // url ?: return@let
|
||||
|
||||
// SET VISUAL KEYS
|
||||
setKey(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
parentId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
apiName,
|
||||
url,
|
||||
currentType,
|
||||
currentHeaderName,
|
||||
currentPoster,
|
||||
parentId,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
|
||||
setKey(
|
||||
getFolderName(
|
||||
DOWNLOAD_EPISODE_CACHE,
|
||||
parentId.toString()
|
||||
), // 3 deep folder for faster acess
|
||||
episode.id.toString(),
|
||||
VideoDownloadHelper.DownloadEpisodeCached(
|
||||
episode.name,
|
||||
episode.poster,
|
||||
episode.episode,
|
||||
episode.season,
|
||||
episode.id,
|
||||
parentId,
|
||||
episode.rating,
|
||||
episode.description,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
|
||||
// DOWNLOAD VIDEO
|
||||
VideoDownloadManager.downloadEpisodeUsingWorker(
|
||||
context,
|
||||
src,//url ?: return,
|
||||
folder,
|
||||
meta,
|
||||
links
|
||||
)
|
||||
|
||||
// 1. Checks if the lang should be downloaded
|
||||
// 2. Makes it into the download format
|
||||
// 3. Downloads it as a .vtt file
|
||||
val downloadList = getDownloadSubsLanguageISO639_1()
|
||||
subs?.let { subsList ->
|
||||
subsList.filter {
|
||||
downloadList.contains(
|
||||
SubtitleHelper.fromLanguageToTwoLetters(
|
||||
it.name,
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
.map { ExtractorSubtitleLink(it.name, it.url, "") }
|
||||
.forEach { link ->
|
||||
val fileName = getFileName(context, meta)
|
||||
downloadSubtitle(context, link, fileName, folder)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,6 +405,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
}
|
||||
|
||||
startDownload(
|
||||
activity,
|
||||
episode,
|
||||
currentIsMovie,
|
||||
currentHeaderName,
|
||||
|
@ -737,6 +790,21 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
builder.create().show()
|
||||
}
|
||||
|
||||
fun acquireSingleSubtitleLink(
|
||||
links: List<SubtitleData>,
|
||||
title: String,
|
||||
callback: (SubtitleData) -> Unit
|
||||
) {
|
||||
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
|
||||
|
||||
builder.setTitle(title)
|
||||
builder.setItems(links.map { it.name }.toTypedArray()) { dia, which ->
|
||||
callback.invoke(links[which])
|
||||
dia?.dismiss()
|
||||
}
|
||||
builder.create().show()
|
||||
}
|
||||
|
||||
fun acquireSingeExtractorLink(title: String, callback: (ExtractorLink) -> Unit) {
|
||||
acquireSingleExtractorLink(sortUrls(currentLinks ?: return), title, callback)
|
||||
}
|
||||
|
@ -839,6 +907,29 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
}
|
||||
}
|
||||
|
||||
ACTION_DOWNLOAD_EPISODE_SUBTITLE -> {
|
||||
acquireSingleSubtitleLink(
|
||||
sortSubs(
|
||||
currentSubs ?: return@main
|
||||
),//(currentLinks ?: return@main).filter { !it.isM3u8 },
|
||||
getString(R.string.episode_action_download_subtitle)
|
||||
) { link ->
|
||||
downloadSubtitle(
|
||||
context,
|
||||
link,
|
||||
getMeta(
|
||||
episodeClick.data,
|
||||
currentHeaderName ?: return@acquireSingleSubtitleLink,
|
||||
apiName,
|
||||
currentPoster ?: return@acquireSingleSubtitleLink,
|
||||
currentIsMovie ?: return@acquireSingleSubtitleLink,
|
||||
currentType ?: return@acquireSingleSubtitleLink
|
||||
)
|
||||
)
|
||||
showToast(activity, R.string.download_started, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
ACTION_SHOW_OPTIONS -> {
|
||||
context?.let { ctx ->
|
||||
val builder = AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
||||
|
@ -862,6 +953,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
val add = when (opv) {
|
||||
ACTION_CHROME_CAST_EPISODE -> isConnected
|
||||
ACTION_CHROME_CAST_MIRROR -> isConnected
|
||||
ACTION_DOWNLOAD_EPISODE_SUBTITLE -> !currentSubs.isNullOrEmpty()
|
||||
ACTION_DOWNLOAD_EPISODE -> hasDownloadSupport
|
||||
ACTION_DOWNLOAD_MIRROR -> hasDownloadSupport
|
||||
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> context?.isAppInstalled(
|
||||
|
@ -1015,21 +1107,20 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
),//(currentLinks ?: return@main).filter { !it.isM3u8 },
|
||||
getString(R.string.episode_action_download_mirror)
|
||||
) { link ->
|
||||
main {
|
||||
startDownload(
|
||||
episodeClick.data,
|
||||
currentIsMovie ?: return@main,
|
||||
currentHeaderName ?: return@main,
|
||||
currentType ?: return@main,
|
||||
currentPoster ?: return@main,
|
||||
apiName,
|
||||
currentId ?: return@main,
|
||||
url ?: return@main,
|
||||
listOf(link),
|
||||
sortSubs(currentSubs ?: return@main)
|
||||
)
|
||||
showToast(activity, R.string.download_started, Toast.LENGTH_SHORT)
|
||||
}
|
||||
startDownload(
|
||||
context,
|
||||
episodeClick.data,
|
||||
currentIsMovie ?: return@acquireSingleExtractorLink,
|
||||
currentHeaderName ?: return@acquireSingleExtractorLink,
|
||||
currentType ?: return@acquireSingleExtractorLink,
|
||||
currentPoster ?: return@acquireSingleExtractorLink,
|
||||
apiName,
|
||||
currentId ?: return@acquireSingleExtractorLink,
|
||||
url ?: return@acquireSingleExtractorLink,
|
||||
listOf(link),
|
||||
sortSubs(currentSubs ?: return@acquireSingleExtractorLink),
|
||||
)
|
||||
showToast(activity, R.string.download_started, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
|||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.services.VideoDownloadService
|
||||
|
@ -116,7 +117,8 @@ object VideoDownloadManager {
|
|||
@JsonProperty("poster") val poster: String?,
|
||||
@JsonProperty("name") val name: String?,
|
||||
@JsonProperty("season") val season: Int?,
|
||||
@JsonProperty("episode") val episode: Int?
|
||||
@JsonProperty("episode") val episode: Int?,
|
||||
@JsonProperty("type") val type: TvType?,
|
||||
)
|
||||
|
||||
data class DownloadItem(
|
||||
|
@ -1335,6 +1337,33 @@ object VideoDownloadManager {
|
|||
return SUCCESS_DOWNLOAD_DONE
|
||||
}
|
||||
|
||||
fun getFileName(context: Context, metadata: DownloadEpisodeMetadata): String {
|
||||
return getFileName(context, metadata.name, metadata.episode, metadata.season)
|
||||
}
|
||||
|
||||
private fun getFileName(context: Context, epName: String?, episode: Int?, season: Int?): String {
|
||||
// kinda ugly ik
|
||||
return sanitizeFilename(
|
||||
if (epName == null) {
|
||||
if (season != null) {
|
||||
"${context.getString(R.string.season)} $season ${context.getString(R.string.episode)} $episode"
|
||||
} else {
|
||||
"${context.getString(R.string.episode)} $episode"
|
||||
}
|
||||
} else {
|
||||
if (episode != null) {
|
||||
if (season != null) {
|
||||
"${context.getString(R.string.season)} $season ${context.getString(R.string.episode)} $episode - $epName"
|
||||
} else {
|
||||
"${context.getString(R.string.episode)} $episode - $epName"
|
||||
}
|
||||
} else {
|
||||
epName
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun downloadSingleEpisode(
|
||||
context: Context,
|
||||
source: String?,
|
||||
|
@ -1344,19 +1373,7 @@ object VideoDownloadManager {
|
|||
notificationCallback: (Int, Notification) -> Unit,
|
||||
tryResume: Boolean = false,
|
||||
): Int {
|
||||
val name =
|
||||
// kinda ugly ik
|
||||
sanitizeFilename(
|
||||
if (ep.name == null) {
|
||||
"${context.getString(R.string.episode)} ${ep.episode}"
|
||||
} else {
|
||||
if (ep.episode != null) {
|
||||
"${context.getString(R.string.episode)} ${ep.episode} - ${ep.name}"
|
||||
} else {
|
||||
ep.name
|
||||
}
|
||||
}
|
||||
)
|
||||
val name = getFileName(context, ep)
|
||||
|
||||
// Make sure this is cancelled when download is done or cancelled.
|
||||
val extractorJob = ioSafe {
|
||||
|
|
|
@ -150,8 +150,10 @@
|
|||
<item>@string/episode_action_copy_link</item>
|
||||
<item>@string/episode_action_auto_download</item>
|
||||
<item>@string/episode_action_download_mirror</item>
|
||||
<item>@string/episode_action_download_subtitle</item>
|
||||
<item>@string/episode_action_reload_links</item>
|
||||
</array>
|
||||
|
||||
<array name="episode_long_click_options_values">
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
|
@ -161,6 +163,7 @@
|
|||
<item>9</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>13</item>
|
||||
<item>8</item>
|
||||
</array>
|
||||
|
||||
|
|
|
@ -304,15 +304,16 @@
|
|||
<string name="unexpected_error">Unexpected player error</string>
|
||||
<string name="storage_error">Download error, check storage permissions</string>
|
||||
|
||||
<string name="episode_action_chromecast_episode">Chromecast Episode</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast Mirror</string>
|
||||
<string name="episode_action_play_in_app">Play In App</string>
|
||||
<string name="episode_action_play_in_vlc">Play In VLC</string>
|
||||
<string name="episode_action_chromecast_episode">Chromecast episode</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast mirror</string>
|
||||
<string name="episode_action_play_in_app">Play in app</string>
|
||||
<string name="episode_action_play_in_vlc">Play in VLC</string>
|
||||
<string name="episode_action_play_in_browser">Play in browser</string>
|
||||
<string name="episode_action_copy_link">Copy Link</string>
|
||||
<string name="episode_action_auto_download">Auto Download</string>
|
||||
<string name="episode_action_download_mirror">Download Mirror</string>
|
||||
<string name="episode_action_reload_links">Reload Links</string>
|
||||
<string name="episode_action_copy_link">Copy link</string>
|
||||
<string name="episode_action_auto_download">Auto download</string>
|
||||
<string name="episode_action_download_mirror">Download mirror</string>
|
||||
<string name="episode_action_reload_links">Reload links</string>
|
||||
<string name="episode_action_download_subtitle">Download subtitles</string>
|
||||
|
||||
<string name="no_update_found">No Update Found</string>
|
||||
<string name="check_for_update">Check for Update</string>
|
||||
|
|
Loading…
Reference in a new issue