mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Fix some track selection bugs (#649)
## Description Fixes track selection issues with MPV. This was especially focused on libraries that disable embedded subtitles, but also fixes issues where the audio streams are listed before video streams. Also includes test media source JSON and unit tests to verify many scenarios. ### Related issues Fixes #494
This commit is contained in:
parent
6906813bbc
commit
d2e0d527dd
7 changed files with 1278 additions and 28 deletions
|
|
@ -494,8 +494,9 @@ class PlaybackViewModel
|
||||||
if (externalSubtitle == null) {
|
if (externalSubtitle == null) {
|
||||||
val result =
|
val result =
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
TrackSelectionUtils.applyTrackSelections(
|
TrackSelectionUtils.createTrackSelections(
|
||||||
player,
|
onMain { player.trackSelectionParameters },
|
||||||
|
onMain { player.currentTracks },
|
||||||
playerBackend,
|
playerBackend,
|
||||||
true,
|
true,
|
||||||
audioIndex,
|
audioIndex,
|
||||||
|
|
@ -504,6 +505,7 @@ class PlaybackViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (result.bothSelected) {
|
if (result.bothSelected) {
|
||||||
|
onMain { player.trackSelectionParameters = result.trackSelectionParameters }
|
||||||
// TODO lots of duplicate code in this block
|
// TODO lots of duplicate code in this block
|
||||||
Timber.d("Changes tracks audio=$audioIndex, subtitle=$subtitleIndex")
|
Timber.d("Changes tracks audio=$audioIndex, subtitle=$subtitleIndex")
|
||||||
val itemPlayback =
|
val itemPlayback =
|
||||||
|
|
@ -696,8 +698,9 @@ class PlaybackViewModel
|
||||||
Timber.v("onTracksChanged: $tracks")
|
Timber.v("onTracksChanged: $tracks")
|
||||||
if (tracks.groups.isNotEmpty()) {
|
if (tracks.groups.isNotEmpty()) {
|
||||||
val result =
|
val result =
|
||||||
TrackSelectionUtils.applyTrackSelections(
|
TrackSelectionUtils.createTrackSelections(
|
||||||
player,
|
player.trackSelectionParameters,
|
||||||
|
player.currentTracks,
|
||||||
playerBackend,
|
playerBackend,
|
||||||
source.supportsDirectPlay,
|
source.supportsDirectPlay,
|
||||||
audioIndex.takeIf { transcodeType == PlayMethod.DIRECT_PLAY },
|
audioIndex.takeIf { transcodeType == PlayMethod.DIRECT_PLAY },
|
||||||
|
|
@ -705,6 +708,8 @@ class PlaybackViewModel
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
if (result.bothSelected) {
|
if (result.bothSelected) {
|
||||||
|
player.trackSelectionParameters =
|
||||||
|
result.trackSelectionParameters
|
||||||
player.removeListener(this)
|
player.removeListener(this)
|
||||||
}
|
}
|
||||||
viewModelScope.launchIO { loadSubtitleDelay() }
|
viewModelScope.launchIO { loadSubtitleDelay() }
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ package com.github.damontecres.wholphin.ui.playback
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.Format
|
import androidx.media3.common.Format
|
||||||
import androidx.media3.common.Player
|
|
||||||
import androidx.media3.common.TrackSelectionOverride
|
import androidx.media3.common.TrackSelectionOverride
|
||||||
|
import androidx.media3.common.TrackSelectionParameters
|
||||||
|
import androidx.media3.common.Tracks
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
||||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||||
|
|
@ -12,24 +13,24 @@ import org.jellyfin.sdk.model.api.MediaStream
|
||||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||||
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
|
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
object TrackSelectionUtils {
|
object TrackSelectionUtils {
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
fun applyTrackSelections(
|
fun createTrackSelections(
|
||||||
player: Player,
|
trackSelectionParams: TrackSelectionParameters,
|
||||||
|
tracks: Tracks,
|
||||||
playerBackend: PlayerBackend,
|
playerBackend: PlayerBackend,
|
||||||
supportsDirectPlay: Boolean,
|
supportsDirectPlay: Boolean,
|
||||||
audioIndex: Int?,
|
audioIndex: Int?,
|
||||||
subtitleIndex: Int?,
|
subtitleIndex: Int?,
|
||||||
source: MediaSourceInfo,
|
source: MediaSourceInfo,
|
||||||
): TrackSelectionResult {
|
): TrackSelectionResult {
|
||||||
val videoStreamCount = source.videoStreamCount
|
|
||||||
val audioStreamCount = source.audioStreamCount
|
|
||||||
val embeddedSubtitleCount = source.embeddedSubtitleCount
|
val embeddedSubtitleCount = source.embeddedSubtitleCount
|
||||||
val externalSubtitleCount = source.externalSubtitlesCount
|
val externalSubtitleCount = source.externalSubtitlesCount
|
||||||
|
|
||||||
val paramsBuilder = player.trackSelectionParameters.buildUpon()
|
val paramsBuilder = trackSelectionParams.buildUpon()
|
||||||
val tracks = player.currentTracks.groups
|
val groups = tracks.groups
|
||||||
|
|
||||||
val subtitleSelected =
|
val subtitleSelected =
|
||||||
if (subtitleIndex != null && subtitleIndex >= 0) {
|
if (subtitleIndex != null && subtitleIndex >= 0) {
|
||||||
|
|
@ -37,7 +38,7 @@ object TrackSelectionUtils {
|
||||||
if (subtitleIsExternal || supportsDirectPlay) {
|
if (subtitleIsExternal || supportsDirectPlay) {
|
||||||
val chosenTrack =
|
val chosenTrack =
|
||||||
if (subtitleIsExternal && playerBackend == PlayerBackend.EXO_PLAYER) {
|
if (subtitleIsExternal && playerBackend == PlayerBackend.EXO_PLAYER) {
|
||||||
tracks.firstOrNull { group ->
|
groups.firstOrNull { group ->
|
||||||
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
|
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
|
||||||
(0..<group.mediaTrackGroup.length)
|
(0..<group.mediaTrackGroup.length)
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
|
|
@ -45,23 +46,38 @@ object TrackSelectionUtils {
|
||||||
}.any { it.endsWith("e:$subtitleIndex") }
|
}.any { it.endsWith("e:$subtitleIndex") }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val actualEmbeddedCount =
|
||||||
|
groups
|
||||||
|
.filter { group ->
|
||||||
|
group.type == C.TRACK_TYPE_TEXT &&
|
||||||
|
(0..<group.mediaTrackGroup.length)
|
||||||
|
.mapNotNull {
|
||||||
|
group.getTrackFormat(it).id
|
||||||
|
}.none { it.contains("e:") }
|
||||||
|
}.size
|
||||||
val indexToFind =
|
val indexToFind =
|
||||||
calculateIndexToFind(
|
calculateIndexToFind(
|
||||||
subtitleIndex,
|
subtitleIndex,
|
||||||
MediaStreamType.SUBTITLE,
|
MediaStreamType.SUBTITLE,
|
||||||
playerBackend,
|
playerBackend,
|
||||||
videoStreamCount,
|
|
||||||
audioStreamCount,
|
|
||||||
embeddedSubtitleCount,
|
embeddedSubtitleCount,
|
||||||
externalSubtitleCount,
|
externalSubtitleCount,
|
||||||
subtitleIsExternal,
|
subtitleIsExternal,
|
||||||
|
actualEmbeddedCount,
|
||||||
|
source,
|
||||||
)
|
)
|
||||||
Timber.v("Chosen subtitle ($subtitleIndex/$indexToFind) track")
|
Timber.v("Chosen subtitle ($subtitleIndex/$indexToFind) track")
|
||||||
// subtitleIndex - externalSubtitleCount + 1
|
// subtitleIndex - externalSubtitleCount + 1
|
||||||
tracks.firstOrNull { group ->
|
groups.firstOrNull { group ->
|
||||||
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
|
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
|
||||||
(0..<group.mediaTrackGroup.length)
|
(0..<group.mediaTrackGroup.length)
|
||||||
.map {
|
.filter {
|
||||||
|
if (subtitleIsExternal) {
|
||||||
|
group.getTrackFormat(0).id?.contains("e:") == true
|
||||||
|
} else {
|
||||||
|
group.getTrackFormat(0).id?.contains("e:") == false
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
group.getTrackFormat(it).idAsInt
|
group.getTrackFormat(it).idAsInt
|
||||||
}.contains(indexToFind)
|
}.contains(indexToFind)
|
||||||
}
|
}
|
||||||
|
|
@ -95,14 +111,14 @@ object TrackSelectionUtils {
|
||||||
audioIndex,
|
audioIndex,
|
||||||
MediaStreamType.AUDIO,
|
MediaStreamType.AUDIO,
|
||||||
playerBackend,
|
playerBackend,
|
||||||
videoStreamCount,
|
|
||||||
audioStreamCount,
|
|
||||||
embeddedSubtitleCount,
|
embeddedSubtitleCount,
|
||||||
externalSubtitleCount,
|
externalSubtitleCount,
|
||||||
false,
|
false,
|
||||||
|
null,
|
||||||
|
source,
|
||||||
)
|
)
|
||||||
val chosenTrack =
|
val chosenTrack =
|
||||||
tracks.firstOrNull { group ->
|
groups.firstOrNull { group ->
|
||||||
group.type == C.TRACK_TYPE_AUDIO && group.isSupported &&
|
group.type == C.TRACK_TYPE_AUDIO && group.isSupported &&
|
||||||
(0..<group.mediaTrackGroup.length)
|
(0..<group.mediaTrackGroup.length)
|
||||||
.map {
|
.map {
|
||||||
|
|
@ -124,10 +140,7 @@ object TrackSelectionUtils {
|
||||||
} else {
|
} else {
|
||||||
audioIndex == null
|
audioIndex == null
|
||||||
}
|
}
|
||||||
if (audioSelected && subtitleSelected) {
|
return TrackSelectionResult(paramsBuilder.build(), audioSelected, subtitleSelected)
|
||||||
player.trackSelectionParameters = paramsBuilder.build()
|
|
||||||
}
|
|
||||||
return TrackSelectionResult(audioSelected, subtitleSelected)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -137,11 +150,11 @@ object TrackSelectionUtils {
|
||||||
serverIndex: Int,
|
serverIndex: Int,
|
||||||
type: MediaStreamType,
|
type: MediaStreamType,
|
||||||
playerBackend: PlayerBackend,
|
playerBackend: PlayerBackend,
|
||||||
videoStreamCount: Int,
|
|
||||||
audioStreamCount: Int,
|
|
||||||
embeddedSubtitleCount: Int,
|
embeddedSubtitleCount: Int,
|
||||||
externalSubtitleCount: Int,
|
externalSubtitleCount: Int,
|
||||||
subtitleIsExternal: Boolean,
|
subtitleIsExternal: Boolean,
|
||||||
|
actualEmbeddedCount: Int?,
|
||||||
|
source: MediaSourceInfo,
|
||||||
): Int =
|
): Int =
|
||||||
when (playerBackend) {
|
when (playerBackend) {
|
||||||
PlayerBackend.EXO_PLAYER,
|
PlayerBackend.EXO_PLAYER,
|
||||||
|
|
@ -158,13 +171,22 @@ object TrackSelectionUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaStreamType.AUDIO -> {
|
MediaStreamType.AUDIO -> {
|
||||||
serverIndex - externalSubtitleCount - videoStreamCount + 1
|
val videoStreamsBeforeAudioCount =
|
||||||
|
source.mediaStreams
|
||||||
|
.orEmpty()
|
||||||
|
.indexOfFirst { it.type == MediaStreamType.AUDIO } - externalSubtitleCount
|
||||||
|
serverIndex - externalSubtitleCount - videoStreamsBeforeAudioCount + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaStreamType.SUBTITLE -> {
|
MediaStreamType.SUBTITLE -> {
|
||||||
if (subtitleIsExternal) {
|
if (subtitleIsExternal) {
|
||||||
serverIndex + embeddedSubtitleCount + 1
|
// Need to account for the actual embedded count because if the library
|
||||||
|
// disables embedded subtitles, they still exist in the direct played file,
|
||||||
|
// but not included in the MediaStreams list
|
||||||
|
serverIndex + max(actualEmbeddedCount ?: 0, embeddedSubtitleCount) + 1
|
||||||
} else {
|
} else {
|
||||||
|
val videoStreamCount = source.videoStreamCount
|
||||||
|
val audioStreamCount = source.audioStreamCount
|
||||||
serverIndex - externalSubtitleCount - videoStreamCount - audioStreamCount + 1
|
serverIndex - externalSubtitleCount - videoStreamCount - audioStreamCount + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -228,12 +250,14 @@ fun MediaSourceInfo.findExternalSubtitle(subtitleIndex: Int?): MediaStream? = me
|
||||||
fun List<MediaStream>.findExternalSubtitle(subtitleIndex: Int?): MediaStream? =
|
fun List<MediaStream>.findExternalSubtitle(subtitleIndex: Int?): MediaStream? =
|
||||||
subtitleIndex?.let {
|
subtitleIndex?.let {
|
||||||
firstOrNull {
|
firstOrNull {
|
||||||
it.type == MediaStreamType.SUBTITLE && it.deliveryMethod == SubtitleDeliveryMethod.EXTERNAL &&
|
it.type == MediaStreamType.SUBTITLE &&
|
||||||
|
(it.deliveryMethod == SubtitleDeliveryMethod.EXTERNAL || it.isExternal) &&
|
||||||
it.index == subtitleIndex
|
it.index == subtitleIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TrackSelectionResult(
|
data class TrackSelectionResult(
|
||||||
|
val trackSelectionParameters: TrackSelectionParameters,
|
||||||
val audioSelected: Boolean,
|
val audioSelected: Boolean,
|
||||||
val subtitleSelected: Boolean,
|
val subtitleSelected: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
9
app/src/test/java/android/text/TextUtils.java
Normal file
9
app/src/test/java/android/text/TextUtils.java
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package android.text;
|
||||||
|
|
||||||
|
// Mocks static class for non-instrumented unit tests
|
||||||
|
|
||||||
|
public class TextUtils {
|
||||||
|
public static boolean isEmpty(CharSequence str) {
|
||||||
|
return str == null || str.length() == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,591 @@
|
||||||
|
package com.github.damontecres.wholphin.test
|
||||||
|
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.Format
|
||||||
|
import androidx.media3.common.TrackGroup
|
||||||
|
import androidx.media3.common.TrackSelectionParameters
|
||||||
|
import androidx.media3.common.Tracks
|
||||||
|
import com.github.damontecres.wholphin.data.model.TrackIndex
|
||||||
|
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
||||||
|
import com.github.damontecres.wholphin.ui.playback.TrackSelectionUtils
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
|
class TestTrackSelection {
|
||||||
|
/**
|
||||||
|
* Builds the tracks for the `embedded_subs.json` for the given backend
|
||||||
|
*
|
||||||
|
* Note: This is manual based on observation & code review of the playback for that file
|
||||||
|
*/
|
||||||
|
private fun buildEmbeddedTracks(backend: PlayerBackend): Tracks {
|
||||||
|
val formats =
|
||||||
|
if (backend == PlayerBackend.MPV) {
|
||||||
|
val video =
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:1")
|
||||||
|
.setSampleMimeType("video/default")
|
||||||
|
.build()
|
||||||
|
val audios =
|
||||||
|
(1..3).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("$it:$it")
|
||||||
|
.setSampleMimeType("audio/default")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
val subtitles =
|
||||||
|
(1..3).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("${it + 3}:$it")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
(listOf(video) + audios + subtitles)
|
||||||
|
} else {
|
||||||
|
val video =
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("1")
|
||||||
|
.setSampleMimeType("video/default")
|
||||||
|
.build()
|
||||||
|
val audios =
|
||||||
|
(2..4).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("$it")
|
||||||
|
.setSampleMimeType("audio/default")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
val subtitles =
|
||||||
|
(5..7).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("$it")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
(listOf(video) + audios + subtitles)
|
||||||
|
}
|
||||||
|
val groups =
|
||||||
|
formats
|
||||||
|
.map { TrackGroup(it) }
|
||||||
|
.map { Tracks.Group(it, false, intArrayOf(C.FORMAT_HANDLED), booleanArrayOf(false)) }
|
||||||
|
return Tracks(groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the tracks for the `no_embedded_subs.json` for the given backend
|
||||||
|
*
|
||||||
|
* Note: This is manual based on observation & code review of the playback for that file
|
||||||
|
*/
|
||||||
|
private fun buildNoEmbeddedTracks(backend: PlayerBackend): Tracks {
|
||||||
|
val formats =
|
||||||
|
if (backend == PlayerBackend.MPV) {
|
||||||
|
val video =
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:1")
|
||||||
|
.setSampleMimeType("video/default")
|
||||||
|
.build()
|
||||||
|
val audios =
|
||||||
|
(1..3).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("$it:$it")
|
||||||
|
.setSampleMimeType("audio/default")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
val subtitles =
|
||||||
|
(1..3).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("${it + 3}:$it")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build()
|
||||||
|
} +
|
||||||
|
listOf(
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("7:e:4")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
(listOf(video) + audios + subtitles)
|
||||||
|
} else {
|
||||||
|
// ExoPlayer
|
||||||
|
val video =
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:1")
|
||||||
|
.setSampleMimeType("video/default")
|
||||||
|
.build()
|
||||||
|
val audios =
|
||||||
|
(2..4).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:$it")
|
||||||
|
.setSampleMimeType("audio/default")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
val subtitles =
|
||||||
|
(5..7).map {
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:$it")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build()
|
||||||
|
} +
|
||||||
|
listOf(
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("1:e:0")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
(listOf(video) + audios + subtitles)
|
||||||
|
}
|
||||||
|
val groups =
|
||||||
|
formats
|
||||||
|
.map { TrackGroup(it) }
|
||||||
|
.map { Tracks.Group(it, false, intArrayOf(C.FORMAT_HANDLED), booleanArrayOf(false)) }
|
||||||
|
return Tracks(groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the tracks for the `external_subs.json` for the given backend.
|
||||||
|
*
|
||||||
|
* Must supply the desired subtitle index because ExoPlayer uses it.
|
||||||
|
*
|
||||||
|
* Note: This is manual based on observation & code review of the playback for that file
|
||||||
|
*/
|
||||||
|
private fun buildExternalTracks(
|
||||||
|
backend: PlayerBackend,
|
||||||
|
selectedIndex: Int,
|
||||||
|
): Tracks {
|
||||||
|
val formats =
|
||||||
|
if (backend == PlayerBackend.MPV) {
|
||||||
|
val video =
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("1:1")
|
||||||
|
.setSampleMimeType("video/default")
|
||||||
|
.build()
|
||||||
|
val audios =
|
||||||
|
listOf(
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:1")
|
||||||
|
.setSampleMimeType("audio/default")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
val subtitles =
|
||||||
|
listOf(
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("2:1")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build(),
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("3:e:2")
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
(listOf(video) + audios + subtitles)
|
||||||
|
} else {
|
||||||
|
val video =
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:2")
|
||||||
|
.setSampleMimeType("video/default")
|
||||||
|
.build()
|
||||||
|
val audios =
|
||||||
|
listOf(
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:1")
|
||||||
|
.setSampleMimeType("audio/default")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
val subtitles =
|
||||||
|
listOf(
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("0:3") // Embedded
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build(),
|
||||||
|
Format
|
||||||
|
.Builder()
|
||||||
|
.setId("1:e:$selectedIndex") // External
|
||||||
|
.setSampleMimeType("text/default")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
(listOf(video) + audios + subtitles)
|
||||||
|
}
|
||||||
|
val groups =
|
||||||
|
formats
|
||||||
|
.map { TrackGroup(it) }
|
||||||
|
.map { Tracks.Group(it, false, intArrayOf(C.FORMAT_HANDLED), booleanArrayOf(false)) }
|
||||||
|
return Tracks(groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TrackSelectionParameters.getAudioOverride(): Format? {
|
||||||
|
this.overrides.forEach { (trackGroup, trackSelectionOverride) ->
|
||||||
|
if (trackGroup.type == C.TRACK_TYPE_AUDIO) {
|
||||||
|
return trackGroup.getFormat(trackSelectionOverride.trackIndices.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TrackSelectionParameters.getSubtitleOverride(): Format? {
|
||||||
|
this.overrides.forEach { (trackGroup, trackSelectionOverride) ->
|
||||||
|
if (trackGroup.type == C.TRACK_TYPE_TEXT) {
|
||||||
|
return trackGroup.getFormat(trackSelectionOverride.trackIndices.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test MPV embedded`() {
|
||||||
|
val resource = javaClass.classLoader?.getResource("embedded_subs.json")
|
||||||
|
Assert.assertNotNull(resource)
|
||||||
|
val fileContents = Paths.get(resource!!.toURI()).readText()
|
||||||
|
val source = Json.decodeFromString<MediaSourceInfo>(fileContents)
|
||||||
|
val tracks = buildEmbeddedTracks(PlayerBackend.MPV)
|
||||||
|
Assert.assertEquals(7, source.mediaStreams?.size)
|
||||||
|
|
||||||
|
val trackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.MPV,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 1,
|
||||||
|
subtitleIndex = 4,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("1:1", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("4:1", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.MPV,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 2,
|
||||||
|
subtitleIndex = 4,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("2:2", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("4:1", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.MPV,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 1,
|
||||||
|
subtitleIndex = TrackIndex.DISABLED,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("1:1", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals(null, result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ExoPlayer embedded`() {
|
||||||
|
val resource = javaClass.classLoader?.getResource("embedded_subs.json")
|
||||||
|
Assert.assertNotNull(resource)
|
||||||
|
val fileContents = Paths.get(resource!!.toURI()).readText()
|
||||||
|
val source = Json.decodeFromString<MediaSourceInfo>(fileContents)
|
||||||
|
val tracks = buildEmbeddedTracks(PlayerBackend.EXO_PLAYER)
|
||||||
|
Assert.assertEquals(7, source.mediaStreams?.size)
|
||||||
|
|
||||||
|
val trackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 1,
|
||||||
|
subtitleIndex = 4,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("2", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("5", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 2,
|
||||||
|
subtitleIndex = 4,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("3", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("5", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 2,
|
||||||
|
subtitleIndex = 6,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("3", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("7", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 1,
|
||||||
|
subtitleIndex = TrackIndex.DISABLED,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("2", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals(null, result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test MPV no embedded`() {
|
||||||
|
val resource = javaClass.classLoader?.getResource("no_embedded_subs.json")
|
||||||
|
Assert.assertNotNull(resource)
|
||||||
|
val fileContents = Paths.get(resource!!.toURI()).readText()
|
||||||
|
val source = Json.decodeFromString<MediaSourceInfo>(fileContents)
|
||||||
|
val tracks = buildNoEmbeddedTracks(PlayerBackend.MPV)
|
||||||
|
Assert.assertEquals(5, source.mediaStreams?.size)
|
||||||
|
|
||||||
|
val trackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.MPV,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 2,
|
||||||
|
subtitleIndex = 0,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("1:1", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("7:e:4", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.MPV,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 3,
|
||||||
|
subtitleIndex = 0,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("2:2", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("7:e:4", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ExoPlayer no embedded`() {
|
||||||
|
val resource = javaClass.classLoader?.getResource("no_embedded_subs.json")
|
||||||
|
Assert.assertNotNull(resource)
|
||||||
|
val fileContents = Paths.get(resource!!.toURI()).readText()
|
||||||
|
val source = Json.decodeFromString<MediaSourceInfo>(fileContents)
|
||||||
|
val tracks = buildNoEmbeddedTracks(PlayerBackend.EXO_PLAYER)
|
||||||
|
Assert.assertEquals(5, source.mediaStreams?.size)
|
||||||
|
|
||||||
|
val trackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 2,
|
||||||
|
subtitleIndex = 0,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.bothSelected)
|
||||||
|
Assert.assertEquals("0:2", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("1:e:0", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test MPV external`() {
|
||||||
|
val resource = javaClass.classLoader?.getResource("external_subs.json")
|
||||||
|
Assert.assertNotNull(resource)
|
||||||
|
val fileContents = Paths.get(resource!!.toURI()).readText()
|
||||||
|
val source = Json.decodeFromString<MediaSourceInfo>(fileContents)
|
||||||
|
val tracks = buildExternalTracks(PlayerBackend.MPV, 0)
|
||||||
|
Assert.assertEquals(6, source.mediaStreams?.size)
|
||||||
|
|
||||||
|
val trackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.MPV,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 3,
|
||||||
|
subtitleIndex = 0,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.audioSelected)
|
||||||
|
Assert.assertTrue(result.subtitleSelected)
|
||||||
|
Assert.assertEquals("0:1", result.trackSelectionParameters.getAudioOverride()?.id)
|
||||||
|
Assert.assertEquals("3:e:2", result.trackSelectionParameters.getSubtitleOverride()?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select embedded subtitles
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.MPV,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 3,
|
||||||
|
subtitleIndex = 5,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.audioSelected)
|
||||||
|
Assert.assertTrue(result.subtitleSelected)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"0:1",
|
||||||
|
result.trackSelectionParameters.getAudioOverride()?.id,
|
||||||
|
)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"2:1",
|
||||||
|
result.trackSelectionParameters.getSubtitleOverride()?.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ExoPlayer external`() {
|
||||||
|
val resource = javaClass.classLoader?.getResource("external_subs.json")
|
||||||
|
Assert.assertNotNull(resource)
|
||||||
|
val fileContents = Paths.get(resource!!.toURI()).readText()
|
||||||
|
val source = Json.decodeFromString<MediaSourceInfo>(fileContents)
|
||||||
|
|
||||||
|
buildExternalTracks(PlayerBackend.EXO_PLAYER, 0).also { tracks ->
|
||||||
|
Assert.assertEquals(6, source.mediaStreams?.size)
|
||||||
|
|
||||||
|
val trackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 3,
|
||||||
|
subtitleIndex = 0,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.audioSelected)
|
||||||
|
Assert.assertTrue(result.subtitleSelected)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"0:1",
|
||||||
|
result.trackSelectionParameters.getAudioOverride()?.id,
|
||||||
|
)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"1:e:0",
|
||||||
|
result.trackSelectionParameters.getSubtitleOverride()?.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select embedded subtitles
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 3,
|
||||||
|
subtitleIndex = 5,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.audioSelected)
|
||||||
|
Assert.assertTrue(result.subtitleSelected)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"0:1",
|
||||||
|
result.trackSelectionParameters.getAudioOverride()?.id,
|
||||||
|
)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"0:3",
|
||||||
|
result.trackSelectionParameters.getSubtitleOverride()?.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildExternalTracks(PlayerBackend.EXO_PLAYER, 2).also { tracks ->
|
||||||
|
Assert.assertEquals(6, source.mediaStreams?.size)
|
||||||
|
|
||||||
|
val trackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
|
||||||
|
TrackSelectionUtils
|
||||||
|
.createTrackSelections(
|
||||||
|
trackSelectionParams = trackSelectionParameters,
|
||||||
|
tracks = tracks,
|
||||||
|
playerBackend = PlayerBackend.EXO_PLAYER,
|
||||||
|
supportsDirectPlay = true,
|
||||||
|
audioIndex = 3,
|
||||||
|
subtitleIndex = 2,
|
||||||
|
source = source,
|
||||||
|
).also { result ->
|
||||||
|
Assert.assertTrue(result.audioSelected)
|
||||||
|
Assert.assertTrue(result.subtitleSelected)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"0:1",
|
||||||
|
result.trackSelectionParameters.getAudioOverride()?.id,
|
||||||
|
)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"1:e:2",
|
||||||
|
result.trackSelectionParameters.getSubtitleOverride()?.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
235
app/src/test/resources/embedded_subs.json
Normal file
235
app/src/test/resources/embedded_subs.json
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
{
|
||||||
|
"Protocol": "File",
|
||||||
|
"Id": "",
|
||||||
|
"Path": "",
|
||||||
|
"Type": "Default",
|
||||||
|
"Container": "mkv",
|
||||||
|
"Size": 651217902,
|
||||||
|
"Name": "",
|
||||||
|
"IsRemote": false,
|
||||||
|
"ETag": "",
|
||||||
|
"RunTimeTicks": 14919360000,
|
||||||
|
"ReadAtNativeFramerate": false,
|
||||||
|
"IgnoreDts": false,
|
||||||
|
"IgnoreIndex": false,
|
||||||
|
"GenPtsInput": false,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"IsInfiniteStream": false,
|
||||||
|
"UseMostCompatibleTranscodingProfile": false,
|
||||||
|
"RequiresOpening": false,
|
||||||
|
"RequiresClosing": false,
|
||||||
|
"RequiresLooping": false,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "hevc",
|
||||||
|
"Language": "jpn",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "",
|
||||||
|
"VideoRange": "SDR",
|
||||||
|
"VideoRangeType": "SDR",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"DisplayTitle": "1080p - HEVC - SDR",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"BitRate": 3491934,
|
||||||
|
"BitDepth": 10,
|
||||||
|
"RefFrames": 1,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 1080,
|
||||||
|
"Width": 1448,
|
||||||
|
"AverageFrameRate": 23.809525,
|
||||||
|
"RealFrameRate": 23.809525,
|
||||||
|
"ReferenceFrameRate": 23.809525,
|
||||||
|
"Profile": "Main 10",
|
||||||
|
"Type": "Video",
|
||||||
|
"AspectRatio": "4:3",
|
||||||
|
"Index": 0,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"PixelFormat": "yuv420p10le",
|
||||||
|
"Level": 120,
|
||||||
|
"IsAnamorphic": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "opus",
|
||||||
|
"Language": "jpn",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "JAP Stereo (Opus 112Kbps)",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"DisplayTitle": "JAP Stereo (Opus 112Kbps) - Japanese",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"ChannelLayout": "stereo",
|
||||||
|
"BitRate": 101618,
|
||||||
|
"Channels": 2,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Type": "Audio",
|
||||||
|
"Index": 1,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "aac",
|
||||||
|
"Language": "por",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"DisplayTitle": "Portuguese - AAC - Stereo",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"ChannelLayout": "stereo",
|
||||||
|
"BitRate": 249225,
|
||||||
|
"Channels": 2,
|
||||||
|
"SampleRate": 44100,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Profile": "LC",
|
||||||
|
"Type": "Audio",
|
||||||
|
"Index": 2,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "eac3",
|
||||||
|
"Language": "por",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"DisplayTitle": "Portuguese - Dolby Digital+ - 5.1",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"ChannelLayout": "5.1",
|
||||||
|
"BitRate": 640000,
|
||||||
|
"Channels": 6,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Type": "Audio",
|
||||||
|
"Index": 3,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "ass",
|
||||||
|
"Language": "por",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "ptBR",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "ptBR - Portuguese - Default - ASS",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 4,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "ass",
|
||||||
|
"Language": "por",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "ptBR FORCED",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "ptBR FORCED - Portuguese - ASS",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": true,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 5,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "ass",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "enUS",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "enUS - English - ASS",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 6,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Level": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaAttachments": [],
|
||||||
|
"Formats": [],
|
||||||
|
"Bitrate": 4482777,
|
||||||
|
"RequiredHttpHeaders": {},
|
||||||
|
"TranscodingSubProtocol": "http",
|
||||||
|
"DefaultAudioStreamIndex": 1,
|
||||||
|
"DefaultSubtitleStreamIndex": 6,
|
||||||
|
"HasSegments": true
|
||||||
|
}
|
||||||
208
app/src/test/resources/external_subs.json
Normal file
208
app/src/test/resources/external_subs.json
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
{
|
||||||
|
"Protocol": "File",
|
||||||
|
"Id": "",
|
||||||
|
"Path": "",
|
||||||
|
"Type": "Default",
|
||||||
|
"Container": "mkv",
|
||||||
|
"Size": 2147179830,
|
||||||
|
"Name": "",
|
||||||
|
"IsRemote": false,
|
||||||
|
"ETag": "",
|
||||||
|
"RunTimeTicks": 12793200000,
|
||||||
|
"ReadAtNativeFramerate": false,
|
||||||
|
"IgnoreDts": false,
|
||||||
|
"IgnoreIndex": false,
|
||||||
|
"GenPtsInput": false,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"IsInfiniteStream": false,
|
||||||
|
"UseMostCompatibleTranscodingProfile": false,
|
||||||
|
"RequiresOpening": false,
|
||||||
|
"RequiresClosing": false,
|
||||||
|
"RequiresLooping": false,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "subrip",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "English - SUBRIP - External",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "",
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "subrip",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "0",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "0 - English - SUBRIP - External",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 1,
|
||||||
|
"IsExternal": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "",
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "subrip",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "English - SUBRIP - External",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 2,
|
||||||
|
"IsExternal": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "",
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "eac3",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"DisplayTitle": "English - Dolby Digital+ - 5.1 - Default",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"ChannelLayout": "5.1",
|
||||||
|
"BitRate": 640000,
|
||||||
|
"Channels": 6,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Type": "Audio",
|
||||||
|
"Index": 3,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "h264",
|
||||||
|
"ColorSpace": "bt709",
|
||||||
|
"ColorTransfer": "bt709",
|
||||||
|
"ColorPrimaries": "bt709",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"VideoRange": "SDR",
|
||||||
|
"VideoRangeType": "SDR",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"DisplayTitle": "1080p H264 SDR",
|
||||||
|
"NalLengthSize": "4",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": true,
|
||||||
|
"BitRate": 13427007,
|
||||||
|
"BitDepth": 8,
|
||||||
|
"RefFrames": 1,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 1080,
|
||||||
|
"Width": 1920,
|
||||||
|
"AverageFrameRate": 23.976025,
|
||||||
|
"RealFrameRate": 23.976025,
|
||||||
|
"ReferenceFrameRate": 23.976025,
|
||||||
|
"Profile": "High",
|
||||||
|
"Type": "Video",
|
||||||
|
"AspectRatio": "16:9",
|
||||||
|
"Index": 4,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"PixelFormat": "yuv420p",
|
||||||
|
"Level": 40,
|
||||||
|
"IsAnamorphic": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "subrip",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "Undefined - Default - SUBRIP",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 5,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Level": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaAttachments": [],
|
||||||
|
"Formats": [],
|
||||||
|
"Bitrate": 14067007,
|
||||||
|
"RequiredHttpHeaders": {},
|
||||||
|
"TranscodingSubProtocol": "http",
|
||||||
|
"DefaultAudioStreamIndex": 3,
|
||||||
|
"DefaultSubtitleStreamIndex": 0,
|
||||||
|
"HasSegments": true
|
||||||
|
}
|
||||||
178
app/src/test/resources/no_embedded_subs.json
Normal file
178
app/src/test/resources/no_embedded_subs.json
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
{
|
||||||
|
"Protocol": "File",
|
||||||
|
"Id": "",
|
||||||
|
"Path": "",
|
||||||
|
"Type": "Default",
|
||||||
|
"Container": "mkv",
|
||||||
|
"Size": 651217902,
|
||||||
|
"Name": "",
|
||||||
|
"IsRemote": false,
|
||||||
|
"ETag": "",
|
||||||
|
"RunTimeTicks": 14919360000,
|
||||||
|
"ReadAtNativeFramerate": false,
|
||||||
|
"IgnoreDts": false,
|
||||||
|
"IgnoreIndex": false,
|
||||||
|
"GenPtsInput": false,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"IsInfiniteStream": false,
|
||||||
|
"UseMostCompatibleTranscodingProfile": false,
|
||||||
|
"RequiresOpening": false,
|
||||||
|
"RequiresClosing": false,
|
||||||
|
"RequiresLooping": false,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "subrip",
|
||||||
|
"Language": "spa",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedUndefined": "Undefined",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedForced": "Forced",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"LocalizedHearingImpaired": "Hearing Impaired",
|
||||||
|
"DisplayTitle": "Spanish - SUBRIP - External",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"Type": "Subtitle",
|
||||||
|
"Index": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "",
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "hevc",
|
||||||
|
"Language": "jpn",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "",
|
||||||
|
"VideoRange": "SDR",
|
||||||
|
"VideoRangeType": "SDR",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"DisplayTitle": "1080p - HEVC - SDR",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"BitRate": 3491934,
|
||||||
|
"BitDepth": 10,
|
||||||
|
"RefFrames": 1,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Height": 1080,
|
||||||
|
"Width": 1448,
|
||||||
|
"AverageFrameRate": 23.809525,
|
||||||
|
"RealFrameRate": 23.809525,
|
||||||
|
"ReferenceFrameRate": 23.809525,
|
||||||
|
"Profile": "Main 10",
|
||||||
|
"Type": "Video",
|
||||||
|
"AspectRatio": "4:3",
|
||||||
|
"Index": 1,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"PixelFormat": "yuv420p10le",
|
||||||
|
"Level": 120,
|
||||||
|
"IsAnamorphic": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "opus",
|
||||||
|
"Language": "jpn",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "Stereo (Opus 112Kbps)",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"DisplayTitle": "Stereo (Opus 112Kbps) - Japanese",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"ChannelLayout": "stereo",
|
||||||
|
"BitRate": 101618,
|
||||||
|
"Channels": 2,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Type": "Audio",
|
||||||
|
"Index": 2,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "aac",
|
||||||
|
"Language": "por",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"DisplayTitle": "Portuguese - AAC - Stereo",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"ChannelLayout": "stereo",
|
||||||
|
"BitRate": 249225,
|
||||||
|
"Channels": 2,
|
||||||
|
"SampleRate": 44100,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Profile": "LC",
|
||||||
|
"Type": "Audio",
|
||||||
|
"Index": 3,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"Level": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "eac3",
|
||||||
|
"Language": "por",
|
||||||
|
"TimeBase": "1/1000",
|
||||||
|
"Title": "",
|
||||||
|
"VideoRange": "Unknown",
|
||||||
|
"VideoRangeType": "Unknown",
|
||||||
|
"AudioSpatialFormat": "None",
|
||||||
|
"LocalizedDefault": "Default",
|
||||||
|
"LocalizedExternal": "External",
|
||||||
|
"DisplayTitle": "Portuguese - Dolby Digital+ - 5.1",
|
||||||
|
"IsInterlaced": false,
|
||||||
|
"IsAVC": false,
|
||||||
|
"ChannelLayout": "5.1",
|
||||||
|
"BitRate": 640000,
|
||||||
|
"Channels": 6,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": false,
|
||||||
|
"IsForced": false,
|
||||||
|
"IsHearingImpaired": false,
|
||||||
|
"Type": "Audio",
|
||||||
|
"Index": 4,
|
||||||
|
"IsExternal": false,
|
||||||
|
"IsTextSubtitleStream": false,
|
||||||
|
"SupportsExternalStream": false,
|
||||||
|
"Level": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaAttachments": [],
|
||||||
|
"Formats": [],
|
||||||
|
"Bitrate": 4482777,
|
||||||
|
"RequiredHttpHeaders": {},
|
||||||
|
"TranscodingSubProtocol": "http",
|
||||||
|
"DefaultAudioStreamIndex": 2,
|
||||||
|
"HasSegments": true
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue