added download subtitles option and fixed subtitle download

This commit is contained in:
LagradOst 2022-04-03 22:14:51 +02:00
parent d5a4a65ced
commit 10c945f497
6 changed files with 257 additions and 139 deletions

View file

@ -71,6 +71,9 @@ class DownloadFileGenerator(
.removeSuffix(".vtt") .removeSuffix(".vtt")
.removeSuffix(".srt") .removeSuffix(".srt")
.removeSuffix(".txt") .removeSuffix(".txt")
.trim()
.removePrefix("(")
.removeSuffix(")")
subtitleCallback( subtitleCallback(
SubtitleData( SubtitleData(

View file

@ -48,6 +48,9 @@ const val ACTION_SHOW_OPTIONS = 10
const val ACTION_CLICK_DEFAULT = 11 const val ACTION_CLICK_DEFAULT = 11
const val ACTION_SHOW_TOAST = 12 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) data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter( class EpisodeAdapter(

View file

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE import android.content.Context.CLIPBOARD_SERVICE
import android.content.Intent import android.content.Intent
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.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.getCastSession
import com.lagradost.cloudstream3.CommonActivity.showToast 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.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.CastHelper.startCast 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.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getFolderName import com.lagradost.cloudstream3.utils.DataStore.getFolderName
import com.lagradost.cloudstream3.utils.DataStore.setKey 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.requestRW
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
@ -205,7 +207,80 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
private var updateUIListener: (() -> Unit)? = null 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, episode: ResultEpisode,
currentIsMovie: Boolean, currentIsMovie: Boolean,
currentHeaderName: String, currentHeaderName: String,
@ -217,110 +292,87 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
links: List<ExtractorLink>, links: List<ExtractorLink>,
subs: List<SubtitleData>? subs: List<SubtitleData>?
) { ) {
val titleName = sanitizeFilename(currentHeaderName) try {
if (context == null) return
val meta = VideoDownloadManager.DownloadEpisodeMetadata( val meta =
episode.id, getMeta(
titleName, episode,
apiName, currentHeaderName,
episode.poster ?: currentPoster, apiName,
episode.name, currentPoster,
if (currentIsMovie) null else episode.season, currentIsMovie,
if (currentIsMovie) null else episode.episode currentType
)
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
)
) )
}
.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) { val folder = getFolder(currentType, currentHeaderName)
normalSafeApiCall {
VideoDownloadManager.downloadThing( val src = "$DOWNLOAD_NAVIGATE_TO/$parentId" // url ?: return@let
context ?: return@normalSafeApiCall,
link, // SET VISUAL KEYS
fileName, setKey(
folder, DOWNLOAD_HEADER_CACHE,
"vtt", parentId.toString(),
false, VideoDownloadHelper.DownloadHeaderCached(
null apiName,
) { url,
// no notification 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( startDownload(
activity,
episode, episode,
currentIsMovie, currentIsMovie,
currentHeaderName, currentHeaderName,
@ -737,6 +790,21 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
builder.create().show() 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) { fun acquireSingeExtractorLink(title: String, callback: (ExtractorLink) -> Unit) {
acquireSingleExtractorLink(sortUrls(currentLinks ?: return), title, callback) 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 -> { ACTION_SHOW_OPTIONS -> {
context?.let { ctx -> context?.let { ctx ->
val builder = AlertDialog.Builder(ctx, R.style.AlertDialogCustom) val builder = AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
@ -862,6 +953,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
val add = when (opv) { val add = when (opv) {
ACTION_CHROME_CAST_EPISODE -> isConnected ACTION_CHROME_CAST_EPISODE -> isConnected
ACTION_CHROME_CAST_MIRROR -> isConnected ACTION_CHROME_CAST_MIRROR -> isConnected
ACTION_DOWNLOAD_EPISODE_SUBTITLE -> !currentSubs.isNullOrEmpty()
ACTION_DOWNLOAD_EPISODE -> hasDownloadSupport ACTION_DOWNLOAD_EPISODE -> hasDownloadSupport
ACTION_DOWNLOAD_MIRROR -> hasDownloadSupport ACTION_DOWNLOAD_MIRROR -> hasDownloadSupport
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> context?.isAppInstalled( ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> context?.isAppInstalled(
@ -1015,21 +1107,20 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
),//(currentLinks ?: return@main).filter { !it.isM3u8 }, ),//(currentLinks ?: return@main).filter { !it.isM3u8 },
getString(R.string.episode_action_download_mirror) getString(R.string.episode_action_download_mirror)
) { link -> ) { link ->
main { startDownload(
startDownload( context,
episodeClick.data, episodeClick.data,
currentIsMovie ?: return@main, currentIsMovie ?: return@acquireSingleExtractorLink,
currentHeaderName ?: return@main, currentHeaderName ?: return@acquireSingleExtractorLink,
currentType ?: return@main, currentType ?: return@acquireSingleExtractorLink,
currentPoster ?: return@main, currentPoster ?: return@acquireSingleExtractorLink,
apiName, apiName,
currentId ?: return@main, currentId ?: return@acquireSingleExtractorLink,
url ?: return@main, url ?: return@acquireSingleExtractorLink,
listOf(link), listOf(link),
sortSubs(currentSubs ?: return@main) sortSubs(currentSubs ?: return@acquireSingleExtractorLink),
) )
showToast(activity, R.string.download_started, Toast.LENGTH_SHORT) showToast(activity, R.string.download_started, Toast.LENGTH_SHORT)
}
} }
} }
} }

View file

@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.services.VideoDownloadService import com.lagradost.cloudstream3.services.VideoDownloadService
@ -116,7 +117,8 @@ object VideoDownloadManager {
@JsonProperty("poster") val poster: String?, @JsonProperty("poster") val poster: String?,
@JsonProperty("name") val name: String?, @JsonProperty("name") val name: String?,
@JsonProperty("season") val season: Int?, @JsonProperty("season") val season: Int?,
@JsonProperty("episode") val episode: Int? @JsonProperty("episode") val episode: Int?,
@JsonProperty("type") val type: TvType?,
) )
data class DownloadItem( data class DownloadItem(
@ -1335,6 +1337,33 @@ object VideoDownloadManager {
return SUCCESS_DOWNLOAD_DONE 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( private fun downloadSingleEpisode(
context: Context, context: Context,
source: String?, source: String?,
@ -1344,19 +1373,7 @@ object VideoDownloadManager {
notificationCallback: (Int, Notification) -> Unit, notificationCallback: (Int, Notification) -> Unit,
tryResume: Boolean = false, tryResume: Boolean = false,
): Int { ): Int {
val name = val name = getFileName(context, ep)
// 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
}
}
)
// Make sure this is cancelled when download is done or cancelled. // Make sure this is cancelled when download is done or cancelled.
val extractorJob = ioSafe { val extractorJob = ioSafe {

View file

@ -150,8 +150,10 @@
<item>@string/episode_action_copy_link</item> <item>@string/episode_action_copy_link</item>
<item>@string/episode_action_auto_download</item> <item>@string/episode_action_auto_download</item>
<item>@string/episode_action_download_mirror</item> <item>@string/episode_action_download_mirror</item>
<item>@string/episode_action_download_subtitle</item>
<item>@string/episode_action_reload_links</item> <item>@string/episode_action_reload_links</item>
</array> </array>
<array name="episode_long_click_options_values"> <array name="episode_long_click_options_values">
<item>4</item> <item>4</item>
<item>5</item> <item>5</item>
@ -161,6 +163,7 @@
<item>9</item> <item>9</item>
<item>6</item> <item>6</item>
<item>7</item> <item>7</item>
<item>13</item>
<item>8</item> <item>8</item>
</array> </array>

View file

@ -304,15 +304,16 @@
<string name="unexpected_error">Unexpected player error</string> <string name="unexpected_error">Unexpected player error</string>
<string name="storage_error">Download error, check storage permissions</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_episode">Chromecast episode</string>
<string name="episode_action_chromecast_mirror">Chromecast Mirror</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_app">Play in app</string>
<string name="episode_action_play_in_vlc">Play In VLC</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_play_in_browser">Play in browser</string>
<string name="episode_action_copy_link">Copy Link</string> <string name="episode_action_copy_link">Copy link</string>
<string name="episode_action_auto_download">Auto Download</string> <string name="episode_action_auto_download">Auto download</string>
<string name="episode_action_download_mirror">Download Mirror</string> <string name="episode_action_download_mirror">Download mirror</string>
<string name="episode_action_reload_links">Reload Links</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="no_update_found">No Update Found</string>
<string name="check_for_update">Check for Update</string> <string name="check_for_update">Check for Update</string>