Skip to content

Commit

Permalink
Audio and Video
Browse files Browse the repository at this point in the history
  • Loading branch information
spacecowboy committed Jun 2, 2024
1 parent 0bcafc9 commit 3e66b28
Show file tree
Hide file tree
Showing 5 changed files with 406 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.nononsenseapps.feeder.model.html
import android.util.Log
import com.nononsenseapps.feeder.ui.compose.text.ancestors
import com.nononsenseapps.feeder.ui.compose.text.stripHtml
import com.nononsenseapps.feeder.ui.text.getVideo
import com.nononsenseapps.feeder.util.asUTF8Sequence
import com.nononsenseapps.feeder.util.logDebug
import org.jsoup.Jsoup
Expand Down Expand Up @@ -399,7 +400,7 @@ class HtmlLinearizer {

add(
LinearImage(
candidates = imageCandidates,
sources = imageCandidates,
caption = caption,
link = link,
),
Expand All @@ -417,7 +418,7 @@ class HtmlLinearizer {
.takeIf { it.isNotBlank() }
add(
LinearImage(
candidates = candidates,
sources = candidates,
// Parse a LinearText with annotations from element.attr("alt")
caption =
captionText?.let {
Expand Down Expand Up @@ -555,16 +556,70 @@ class HtmlLinearizer {
}
}

"iframe" -> {
// TODO
}

"rt", "rp" -> {
// Ruby text elements. Not supported.
}

"audio" -> {
val sources =
element.getElementsByTag("source").asSequence()
.mapNotNull { source ->
source.attr("abs:src").takeIf { it.isNotBlank() }?.let { src ->
LinearAudioSource(
uri = src,
mimeType = source.attr("type").ifBlank { null },
)
}
}.toList()
.takeIf { it.isNotEmpty() }

if (sources != null) {
add(LinearAudio(sources))
}
}

"iframe" -> {
getVideo(element.attr("abs:src").ifBlank { null })?.let { video ->
add(
LinearVideo(
sources =
listOf(
LinearVideoSource(
uri = video.src,
link = video.link,
imageThumbnail = video.imageUrl,
widthPx = video.width,
heightPx = video.height,
mimeType = null,
),
),
),
)
}
}

"video" -> {
// not implemented yet.
val width = element.attr("width").toIntOrNull()
val height = element.attr("height").toIntOrNull()
val sources =
element.getElementsByTag("source").asSequence()
.mapNotNull { source ->
source.attr("abs:src").takeIf { it.isNotBlank() }?.let { src ->
LinearVideoSource(
uri = src,
link = src,
imageThumbnail = null,
mimeType = source.attr("type").ifBlank { null },
widthPx = width,
heightPx = height,
)
}
}.toList()
.takeIf { it.isNotEmpty() }

if (sources != null) {
add(LinearVideo(sources))
}
}

else -> {
Expand Down Expand Up @@ -633,7 +688,7 @@ class HtmlLinearizer {
private fun getImageSource(
baseUrl: String,
element: Element,
): List<LinearImageCandidate> {
): List<LinearImageSource> {
val absSrc: String = element.attr("abs:src")
val dataImgUrl: String = element.attr("data-img-url").ifBlank { element.attr("data-src") }
val srcSet: String = element.attr("srcset").ifBlank { element.attr("data-responsive") }
Expand All @@ -659,7 +714,7 @@ class HtmlLinearizer {
?.firstOrNull()
?: ""

val result = mutableListOf<LinearImageCandidate>()
val result = mutableListOf<LinearImageSource>()

try {
srcSet.splitToSequence(", ")
Expand All @@ -671,7 +726,7 @@ class HtmlLinearizer {
}
if (candidate.size == 1) {
result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = StringUtil.resolve(baseUrl, candidate.first()),
pixelDensity = null,
heightPx = null,
Expand All @@ -689,7 +744,7 @@ class HtmlLinearizer {
}

result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = StringUtil.resolve(baseUrl, candidate.first()),
pixelDensity = null,
heightPx = null,
Expand All @@ -707,7 +762,7 @@ class HtmlLinearizer {
}

result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = StringUtil.resolve(baseUrl, candidate.first()),
pixelDensity = density,
heightPx = null,
Expand All @@ -727,7 +782,7 @@ class HtmlLinearizer {
val url = StringUtil.resolve(baseUrl, it)
if (width != null && height != null) {
result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = url,
pixelDensity = null,
screenWidth = null,
Expand All @@ -737,7 +792,7 @@ class HtmlLinearizer {
)
} else {
result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = url,
pixelDensity = null,
heightPx = null,
Expand All @@ -752,7 +807,7 @@ class HtmlLinearizer {
val url = StringUtil.resolve(baseUrl, it)
if (width != null && height != null) {
result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = url,
pixelDensity = null,
screenWidth = null,
Expand All @@ -762,7 +817,7 @@ class HtmlLinearizer {
)
} else {
result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = url,
pixelDensity = null,
screenWidth = null,
Expand All @@ -776,7 +831,7 @@ class HtmlLinearizer {
backgroundImage.takeIf { it.isNotBlank() }?.let {
val url = StringUtil.resolve(baseUrl, it)
result.add(
LinearImageCandidate(
LinearImageSource(
imgUri = url,
pixelDensity = null,
screenWidth = null,
Expand All @@ -791,7 +846,7 @@ class HtmlLinearizer {
return result
}

private fun Element.descendantImageCandidates(baseUrl: String): List<LinearImageCandidate>? {
private fun Element.descendantImageCandidates(baseUrl: String): List<LinearImageSource>? {
// Arstechnica is weird and has images inside divs inside figures
return sequence {
yieldAll(getElementsByTag("img"))
Expand All @@ -803,7 +858,7 @@ class HtmlLinearizer {
.takeIf { it.isNotEmpty() }
}

private fun Element.ancestorImageCandidates(baseUrl: String): List<LinearImageCandidate>? {
private fun Element.ancestorImageCandidates(baseUrl: String): List<LinearImageSource>? {
// Arstechnica is weird and places image details in list items which themselves contain the figure
return ancestors {
it.hasAttr("data-src") || it.hasAttr("data-responsive")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,15 +279,62 @@ val LinearTextBlockStyle.shouldSoftWrap: Boolean
* Represents an image element
*/
data class LinearImage(
val candidates: List<LinearImageCandidate>,
val sources: List<LinearImageSource>,
val caption: LinearText?,
val link: String?,
) : LinearElement

data class LinearImageCandidate(
data class LinearImageSource(
val imgUri: String,
val widthPx: Int?,
val heightPx: Int?,
val pixelDensity: Float?,
val screenWidth: Int?,
)

/**
* Represents a video element
*/
data class LinearVideo(
val sources: List<LinearVideoSource>,
) : LinearElement {
init {
require(sources.isNotEmpty()) { "At least one source must be provided" }
}

val imageThumbnail: String? by lazy {
sources.firstOrNull { it.imageThumbnail != null }?.imageThumbnail
}

val firstSource: LinearVideoSource
get() = sources.first()
}

data class LinearVideoSource(
val uri: String,
// This might be different from the uri, for example for youtube videos where uri is the embed uri
val link: String,
val imageThumbnail: String?,
val widthPx: Int?,
val heightPx: Int?,
val mimeType: String?,
)

/**
* Represents an audio element
*/
data class LinearAudio(
val sources: List<LinearAudioSource>,
) : LinearElement {
init {
require(sources.isNotEmpty()) { "At least one source must be provided" }
}

val firstSource: LinearAudioSource
get() = sources.first()
}

data class LinearAudioSource(
val uri: String,
val mimeType: String?,
)
Loading

0 comments on commit 3e66b28

Please sign in to comment.