Added support for embedded subtitles

This commit is contained in:
Blatzar 2022-05-20 20:25:56 +02:00
parent b4b06b2389
commit 2d0e75b921
8 changed files with 105 additions and 16 deletions

View file

@ -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) {

View file

@ -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)

View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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)