mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Added support for embedded subtitles
This commit is contained in:
parent
b4b06b2389
commit
2d0e75b921
8 changed files with 105 additions and 16 deletions
|
@ -92,6 +92,10 @@ abstract class AbstractPlayerFragment(
|
|||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
open fun embeddedSubtitlesFetched(subtitles: List<SubtitleData>) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
open fun exitedPipMode() {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
@ -338,7 +342,9 @@ abstract class AbstractPlayerFragment(
|
|||
PRELOAD_NEXT_EPISODE_PERCENTAGE,
|
||||
NEXT_WATCH_EPISODE_PERCENTAGE,
|
||||
UPDATE_SYNC_PROGRESS_PERCENTAGE,
|
||||
), subtitlesUpdates = ::subtitlesChanged
|
||||
),
|
||||
subtitlesUpdates = ::subtitlesChanged,
|
||||
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
|
||||
)
|
||||
|
||||
if (player is CS3IPlayer) {
|
||||
|
|
|
@ -27,9 +27,11 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
|||
import com.lagradost.cloudstream3.USER_AGENT
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||
import java.io.File
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
|
@ -95,6 +97,7 @@ class CS3IPlayer : IPlayer {
|
|||
private var prevEpisode: (() -> Unit)? = null
|
||||
|
||||
private var playerUpdated: ((Any?) -> Unit)? = null
|
||||
private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null
|
||||
|
||||
override fun initCallbacks(
|
||||
playerUpdated: (Any?) -> Unit,
|
||||
|
@ -107,6 +110,7 @@ class CS3IPlayer : IPlayer {
|
|||
nextEpisode: (() -> Unit)?,
|
||||
prevEpisode: (() -> Unit)?,
|
||||
subtitlesUpdates: (() -> Unit)?,
|
||||
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?,
|
||||
) {
|
||||
this.playerUpdated = playerUpdated
|
||||
this.updateIsPlaying = updateIsPlaying
|
||||
|
@ -118,6 +122,7 @@ class CS3IPlayer : IPlayer {
|
|||
this.nextEpisode = nextEpisode
|
||||
this.prevEpisode = prevEpisode
|
||||
this.subtitlesUpdates = subtitlesUpdates
|
||||
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
|
||||
}
|
||||
|
||||
// I know, this is not a perfect solution, however it works for fixing subs
|
||||
|
@ -202,7 +207,14 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
trackSelector.setParameters(
|
||||
trackSelector.buildUponParameters()
|
||||
.setPreferredTextLanguage("_$name")
|
||||
.apply {
|
||||
if (subtitle.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO)
|
||||
// The real Language (two letter) is in the url
|
||||
// No underscore as the .url is the actual exoplayer designated language
|
||||
setPreferredTextLanguage(subtitle.url)
|
||||
else
|
||||
setPreferredTextLanguage("_$name")
|
||||
}
|
||||
)
|
||||
|
||||
// ugliest code I have written, it seeks 1ms to *update* the subtitles
|
||||
|
@ -231,16 +243,20 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
override fun getSubtitleOffset(): Long {
|
||||
return currentSubtitleOffset//currentTextRenderer?.getRenderOffsetMs() ?: currentSubtitleOffset
|
||||
return currentSubtitleOffset //currentTextRenderer?.getRenderOffsetMs() ?: currentSubtitleOffset
|
||||
}
|
||||
|
||||
override fun getCurrentPreferredSubtitle(): SubtitleData? {
|
||||
return subtitleHelper.getAllSubtitles().firstOrNull { sub ->
|
||||
exoPlayerSelectedTracks.any {
|
||||
// When embedded the real language is in .url as the real name is a two letter code
|
||||
val realName =
|
||||
if (sub.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) sub.url else sub.name
|
||||
|
||||
// The replace is needed as exoplayer translates _ to -
|
||||
// Also we prefix the languages with _
|
||||
it.second && it.first.replace("-", "").equals(
|
||||
sub.name.replace("-", ""),
|
||||
realName.replace("-", ""),
|
||||
ignoreCase = true
|
||||
)
|
||||
}
|
||||
|
@ -616,9 +632,46 @@ class CS3IPlayer : IPlayer {
|
|||
* Records the current used subtitle/track. Needed as exoplayer seems to have loose track language selection.
|
||||
* */
|
||||
override fun onTracksInfoChanged(tracksInfo: TracksInfo) {
|
||||
exoPlayerSelectedTracks =
|
||||
tracksInfo.trackGroupInfos.mapNotNull { it.trackGroup.getFormat(0).language?.let { lang -> lang to it.isSelected } }
|
||||
subtitlesUpdates?.invoke()
|
||||
fun Format.isSubtitle(): Boolean {
|
||||
return this.sampleMimeType?.contains("video/") == false &&
|
||||
this.sampleMimeType?.contains("audio/") == false
|
||||
}
|
||||
|
||||
normalSafeApiCall {
|
||||
exoPlayerSelectedTracks =
|
||||
tracksInfo.trackGroupInfos.mapNotNull {
|
||||
val format = it.trackGroup.getFormat(0)
|
||||
if (format.isSubtitle())
|
||||
format.language?.let { lang -> lang to it.isSelected }
|
||||
else null
|
||||
}
|
||||
|
||||
val exoPlayerReportedTracks = tracksInfo.trackGroupInfos.mapNotNull {
|
||||
// Filter out unsupported tracks
|
||||
if (it.isSupported)
|
||||
it.trackGroup.getFormat(0)
|
||||
else
|
||||
null
|
||||
}.mapNotNull {
|
||||
// Filter out non subs, already used subs and subs without languages
|
||||
if (!it.isSubtitle() ||
|
||||
// Anything starting with - is not embedded
|
||||
it.language?.startsWith("-") == true ||
|
||||
it.language == null
|
||||
) return@mapNotNull null
|
||||
return@mapNotNull SubtitleData(
|
||||
// Nicer looking displayed names
|
||||
fromTwoLettersToLanguage(it.language!!) ?: it.language!!,
|
||||
// See setPreferredTextLanguage
|
||||
it.language!!,
|
||||
SubtitleOrigin.EMBEDDED_IN_VIDEO,
|
||||
it.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP
|
||||
)
|
||||
}
|
||||
|
||||
embeddedSubtitlesFetched?.invoke(exoPlayerReportedTracks)
|
||||
subtitlesUpdates?.invoke()
|
||||
}
|
||||
super.onTracksInfoChanged(tracksInfo)
|
||||
}
|
||||
|
||||
|
@ -766,6 +819,15 @@ class CS3IPlayer : IPlayer {
|
|||
// TODO
|
||||
throw NotImplementedError()
|
||||
}
|
||||
SubtitleOrigin.EMBEDDED_IN_VIDEO -> {
|
||||
if (offlineSourceFactory != null) {
|
||||
activeSubtitles.add(sub)
|
||||
SingleSampleMediaSource.Factory(offlineSourceFactory)
|
||||
.createMediaSource(subConfig, C.TIME_UNSET)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair(subSources, activeSubtitles)
|
||||
|
|
|
@ -40,7 +40,7 @@ class CustomDecoder : SubtitleDecoder {
|
|||
)
|
||||
val captionRegex = listOf(Regex("""(-\s?|)[\[({][\w\d\s]*?[])}]\s*"""))
|
||||
|
||||
fun trimStr(string: String) : String {
|
||||
fun trimStr(string: String): String {
|
||||
return string.trimStart().trim('\uFEFF', '\u200B').replace(
|
||||
Regex("[\u00A0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u205F]"),
|
||||
" "
|
||||
|
@ -109,10 +109,10 @@ class CustomDecoder : SubtitleDecoder {
|
|||
captionRegex.forEach { rgx ->
|
||||
fullStr = fullStr.replace(rgx, "\n")
|
||||
}
|
||||
fullStr.replace(Regex("(\r\n|\r|\n){2,}"),"\n")
|
||||
fullStr.replace(Regex("(\r\n|\r|\n){2,}"), "\n")
|
||||
|
||||
buff.data = ByteBuffer.wrap(fullStr.toByteArray())
|
||||
} catch (e : Exception) {
|
||||
} catch (e: Exception) {
|
||||
data.position(pos)
|
||||
buff.data = data
|
||||
}
|
||||
|
|
|
@ -82,6 +82,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
return player.setPreferredSubtitles(sub)
|
||||
}
|
||||
|
||||
override fun embeddedSubtitlesFetched(subtitles: List<SubtitleData>) {
|
||||
viewModel.addSubtitles(subtitles.toSet())
|
||||
}
|
||||
|
||||
private fun noSubtitles(): Boolean {
|
||||
return setSubtitles(null)
|
||||
}
|
||||
|
@ -258,7 +262,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
var startSource = 0
|
||||
|
||||
val sortedUrls = sortLinks(useQualitySettings = false)
|
||||
if (sortedUrls.isNullOrEmpty()) {
|
||||
if (sortedUrls.isEmpty()) {
|
||||
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone = true
|
||||
} else {
|
||||
startSource = sortedUrls.indexOf(currentSelectedLink)
|
||||
|
|
|
@ -85,6 +85,7 @@ interface IPlayer {
|
|||
nextEpisode: (() -> Unit)? = null, // this is used by the player to load the next episode
|
||||
prevEpisode: (() -> Unit)? = null, // this is used by the player to load the previous episode
|
||||
subtitlesUpdates: (() -> Unit)? = null, // callback from player to inform that subtitles have updated in some way
|
||||
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles
|
||||
)
|
||||
|
||||
fun updateSubtitleStyle(style: SaveCaptionStyle)
|
||||
|
|
|
@ -93,10 +93,18 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If duplicate nothing will happen
|
||||
* */
|
||||
fun addSubtitles(file: Set<SubtitleData>) {
|
||||
val subs = (_currentSubs.value?.toMutableSet() ?: mutableSetOf())
|
||||
subs.addAll(file)
|
||||
_currentSubs.postValue(subs)
|
||||
val currentSubs = _currentSubs.value ?: emptySet()
|
||||
// Prevent duplicates
|
||||
val allSubs = (currentSubs + file).distinct().toSet()
|
||||
// Do not post if there's nothing new
|
||||
// Posting will refresh subtitles which will in turn
|
||||
// make the subs to english if previously unselected
|
||||
if (allSubs != currentSubs)
|
||||
_currentSubs.postValue(allSubs)
|
||||
}
|
||||
|
||||
private var currentJob: Job? = null
|
||||
|
|
|
@ -23,8 +23,13 @@ enum class SubtitleOrigin {
|
|||
URL,
|
||||
DOWNLOADED_FILE,
|
||||
OPEN_SUBTITLES,
|
||||
EMBEDDED_IN_VIDEO
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name To be displayed in the player
|
||||
* @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend language
|
||||
* */
|
||||
data class SubtitleData(
|
||||
val name: String,
|
||||
val url: String,
|
||||
|
@ -77,6 +82,9 @@ class PlayerSubtitleHelper {
|
|||
// TODO
|
||||
throw NotImplementedError()
|
||||
}
|
||||
SubtitleOrigin.EMBEDDED_IN_VIDEO -> {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -201,7 +201,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
putString(URL_BUNDLE, card.url)
|
||||
putString(API_NAME_BUNDLE, card.apiName)
|
||||
if (card is DataStoreHelper.ResumeWatchingResult) {
|
||||
println("CARD::::: $card")
|
||||
// println("CARD::::: $card")
|
||||
if (card.season != null)
|
||||
putInt(SEASON_BUNDLE, card.season)
|
||||
if (card.episode != null)
|
||||
|
|
Loading…
Reference in a new issue