mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +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) {
|
||||
val result =
|
||||
withContext(Dispatchers.Main) {
|
||||
TrackSelectionUtils.applyTrackSelections(
|
||||
player,
|
||||
TrackSelectionUtils.createTrackSelections(
|
||||
onMain { player.trackSelectionParameters },
|
||||
onMain { player.currentTracks },
|
||||
playerBackend,
|
||||
true,
|
||||
audioIndex,
|
||||
|
|
@ -504,6 +505,7 @@ class PlaybackViewModel
|
|||
)
|
||||
}
|
||||
if (result.bothSelected) {
|
||||
onMain { player.trackSelectionParameters = result.trackSelectionParameters }
|
||||
// TODO lots of duplicate code in this block
|
||||
Timber.d("Changes tracks audio=$audioIndex, subtitle=$subtitleIndex")
|
||||
val itemPlayback =
|
||||
|
|
@ -696,8 +698,9 @@ class PlaybackViewModel
|
|||
Timber.v("onTracksChanged: $tracks")
|
||||
if (tracks.groups.isNotEmpty()) {
|
||||
val result =
|
||||
TrackSelectionUtils.applyTrackSelections(
|
||||
player,
|
||||
TrackSelectionUtils.createTrackSelections(
|
||||
player.trackSelectionParameters,
|
||||
player.currentTracks,
|
||||
playerBackend,
|
||||
source.supportsDirectPlay,
|
||||
audioIndex.takeIf { transcodeType == PlayMethod.DIRECT_PLAY },
|
||||
|
|
@ -705,6 +708,8 @@ class PlaybackViewModel
|
|||
source,
|
||||
)
|
||||
if (result.bothSelected) {
|
||||
player.trackSelectionParameters =
|
||||
result.trackSelectionParameters
|
||||
player.removeListener(this)
|
||||
}
|
||||
viewModelScope.launchIO { loadSubtitleDelay() }
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ package com.github.damontecres.wholphin.ui.playback
|
|||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.TrackSelectionOverride
|
||||
import androidx.media3.common.TrackSelectionParameters
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
||||
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.SubtitleDeliveryMethod
|
||||
import timber.log.Timber
|
||||
import kotlin.math.max
|
||||
|
||||
object TrackSelectionUtils {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun applyTrackSelections(
|
||||
player: Player,
|
||||
fun createTrackSelections(
|
||||
trackSelectionParams: TrackSelectionParameters,
|
||||
tracks: Tracks,
|
||||
playerBackend: PlayerBackend,
|
||||
supportsDirectPlay: Boolean,
|
||||
audioIndex: Int?,
|
||||
subtitleIndex: Int?,
|
||||
source: MediaSourceInfo,
|
||||
): TrackSelectionResult {
|
||||
val videoStreamCount = source.videoStreamCount
|
||||
val audioStreamCount = source.audioStreamCount
|
||||
val embeddedSubtitleCount = source.embeddedSubtitleCount
|
||||
val externalSubtitleCount = source.externalSubtitlesCount
|
||||
|
||||
val paramsBuilder = player.trackSelectionParameters.buildUpon()
|
||||
val tracks = player.currentTracks.groups
|
||||
val paramsBuilder = trackSelectionParams.buildUpon()
|
||||
val groups = tracks.groups
|
||||
|
||||
val subtitleSelected =
|
||||
if (subtitleIndex != null && subtitleIndex >= 0) {
|
||||
|
|
@ -37,7 +38,7 @@ object TrackSelectionUtils {
|
|||
if (subtitleIsExternal || supportsDirectPlay) {
|
||||
val chosenTrack =
|
||||
if (subtitleIsExternal && playerBackend == PlayerBackend.EXO_PLAYER) {
|
||||
tracks.firstOrNull { group ->
|
||||
groups.firstOrNull { group ->
|
||||
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
|
||||
(0..<group.mediaTrackGroup.length)
|
||||
.mapNotNull {
|
||||
|
|
@ -45,23 +46,38 @@ object TrackSelectionUtils {
|
|||
}.any { it.endsWith("e:$subtitleIndex") }
|
||||
}
|
||||
} 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 =
|
||||
calculateIndexToFind(
|
||||
subtitleIndex,
|
||||
MediaStreamType.SUBTITLE,
|
||||
playerBackend,
|
||||
videoStreamCount,
|
||||
audioStreamCount,
|
||||
embeddedSubtitleCount,
|
||||
externalSubtitleCount,
|
||||
subtitleIsExternal,
|
||||
actualEmbeddedCount,
|
||||
source,
|
||||
)
|
||||
Timber.v("Chosen subtitle ($subtitleIndex/$indexToFind) track")
|
||||
// subtitleIndex - externalSubtitleCount + 1
|
||||
tracks.firstOrNull { group ->
|
||||
groups.firstOrNull { group ->
|
||||
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
|
||||
(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
|
||||
}.contains(indexToFind)
|
||||
}
|
||||
|
|
@ -95,14 +111,14 @@ object TrackSelectionUtils {
|
|||
audioIndex,
|
||||
MediaStreamType.AUDIO,
|
||||
playerBackend,
|
||||
videoStreamCount,
|
||||
audioStreamCount,
|
||||
embeddedSubtitleCount,
|
||||
externalSubtitleCount,
|
||||
false,
|
||||
null,
|
||||
source,
|
||||
)
|
||||
val chosenTrack =
|
||||
tracks.firstOrNull { group ->
|
||||
groups.firstOrNull { group ->
|
||||
group.type == C.TRACK_TYPE_AUDIO && group.isSupported &&
|
||||
(0..<group.mediaTrackGroup.length)
|
||||
.map {
|
||||
|
|
@ -124,10 +140,7 @@ object TrackSelectionUtils {
|
|||
} else {
|
||||
audioIndex == null
|
||||
}
|
||||
if (audioSelected && subtitleSelected) {
|
||||
player.trackSelectionParameters = paramsBuilder.build()
|
||||
}
|
||||
return TrackSelectionResult(audioSelected, subtitleSelected)
|
||||
return TrackSelectionResult(paramsBuilder.build(), audioSelected, subtitleSelected)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -137,11 +150,11 @@ object TrackSelectionUtils {
|
|||
serverIndex: Int,
|
||||
type: MediaStreamType,
|
||||
playerBackend: PlayerBackend,
|
||||
videoStreamCount: Int,
|
||||
audioStreamCount: Int,
|
||||
embeddedSubtitleCount: Int,
|
||||
externalSubtitleCount: Int,
|
||||
subtitleIsExternal: Boolean,
|
||||
actualEmbeddedCount: Int?,
|
||||
source: MediaSourceInfo,
|
||||
): Int =
|
||||
when (playerBackend) {
|
||||
PlayerBackend.EXO_PLAYER,
|
||||
|
|
@ -158,13 +171,22 @@ object TrackSelectionUtils {
|
|||
}
|
||||
|
||||
MediaStreamType.AUDIO -> {
|
||||
serverIndex - externalSubtitleCount - videoStreamCount + 1
|
||||
val videoStreamsBeforeAudioCount =
|
||||
source.mediaStreams
|
||||
.orEmpty()
|
||||
.indexOfFirst { it.type == MediaStreamType.AUDIO } - externalSubtitleCount
|
||||
serverIndex - externalSubtitleCount - videoStreamsBeforeAudioCount + 1
|
||||
}
|
||||
|
||||
MediaStreamType.SUBTITLE -> {
|
||||
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 {
|
||||
val videoStreamCount = source.videoStreamCount
|
||||
val audioStreamCount = source.audioStreamCount
|
||||
serverIndex - externalSubtitleCount - videoStreamCount - audioStreamCount + 1
|
||||
}
|
||||
}
|
||||
|
|
@ -228,12 +250,14 @@ fun MediaSourceInfo.findExternalSubtitle(subtitleIndex: Int?): MediaStream? = me
|
|||
fun List<MediaStream>.findExternalSubtitle(subtitleIndex: Int?): MediaStream? =
|
||||
subtitleIndex?.let {
|
||||
firstOrNull {
|
||||
it.type == MediaStreamType.SUBTITLE && it.deliveryMethod == SubtitleDeliveryMethod.EXTERNAL &&
|
||||
it.type == MediaStreamType.SUBTITLE &&
|
||||
(it.deliveryMethod == SubtitleDeliveryMethod.EXTERNAL || it.isExternal) &&
|
||||
it.index == subtitleIndex
|
||||
}
|
||||
}
|
||||
|
||||
data class TrackSelectionResult(
|
||||
val trackSelectionParameters: TrackSelectionParameters,
|
||||
val audioSelected: 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