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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -359,7 +365,7 @@ abstract class AbstractPlayerFragment(
 | 
			
		|||
                        settingsManager.getInt(getString(R.string.video_buffer_disk_key), 0)
 | 
			
		||||
                    val currentPrefBufferSec =
 | 
			
		||||
                        settingsManager.getInt(getString(R.string.video_buffer_length_key), 0)
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                    player.cacheSize = currentPrefCacheSize * 1024L * 1024L
 | 
			
		||||
                    player.simpleCacheSize = currentPrefDiskSize * 1024L * 1024L
 | 
			
		||||
                    player.videoBufferMs = currentPrefBufferSec * 1000L
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue