Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for DeArrow #4276

Merged
merged 1 commit into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/src/main/java/com/github/libretube/api/PipedApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.github.libretube.api.obj.Subscribe
import com.github.libretube.api.obj.Subscribed
import com.github.libretube.api.obj.Subscription
import com.github.libretube.api.obj.Token
import kotlinx.serialization.json.JsonObject
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
Expand Down Expand Up @@ -45,6 +46,11 @@ interface PipedApi {
@Query("category") category: String
): SegmentData

@GET("dearrow")
suspend fun getDeArrowContent(
@Query("videoIds") videoIds: String
): JsonObject

@GET("nextpage/comments/{videoId}")
suspend fun getCommentsNextPage(
@Path("videoId") videoId: String,
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.github.libretube.helpers.ProxyHelper
import com.github.libretube.obj.FreeTubeImportPlaylist
import com.github.libretube.obj.FreeTubeVideo
import com.github.libretube.obj.PipedImportPlaylist
import com.github.libretube.util.deArrow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
Expand All @@ -26,9 +27,7 @@ object PlaylistsHelper {
"[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}".toRegex()

private val token get() = PreferenceHelper.getToken()

val loggedIn: Boolean get() = token.isNotEmpty()

private fun Message.isOk() = this.message == "ok"

suspend fun getPlaylists(): List<Playlists> = withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -64,6 +63,8 @@ object PlaylistsHelper {
relatedStreams = relation.videos.map { it.toStreamItem() }
)
}
}.apply {
relatedStreams = relatedStreams.deArrow()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.db.obj.LocalSubscription
import com.github.libretube.extensions.TAG
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.util.deArrow
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -137,6 +138,6 @@ object SubscriptionHelper {
subscriptions.joinToString(",")
)
}
}
}.deArrow()
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/github/libretube/api/obj/Channel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ data class Channel(
val nextpage: String? = null,
val subscriberCount: Long = 0,
val verified: Boolean = false,
val relatedStreams: List<StreamItem> = emptyList(),
var relatedStreams: List<StreamItem> = emptyList(),
val tabs: List<ChannelTab> = emptyList()
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import kotlinx.serialization.Serializable

@Serializable
data class ChannelTabResponse(
val content: List<ContentItem> = emptyList(),
var content: List<ContentItem> = emptyList(),
val nextpage: String? = null
)
4 changes: 2 additions & 2 deletions app/src/main/java/com/github/libretube/api/obj/ContentItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import kotlinx.serialization.Serializable
data class ContentItem(
val url: String,
val type: String,
val thumbnail: String,
var thumbnail: String,
// Video only attributes
val title: String? = null,
var title: String? = null,
val uploaderUrl: String? = null,
val uploaderAvatar: String? = null,
val duration: Long = -1,
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/github/libretube/api/obj/DeArrowContent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.libretube.api.obj

import kotlinx.serialization.Serializable

@Serializable
data class DeArrowContent(
val thumbnails: List<DeArrowThumbnail>,
val titles: List<DeArrowTitle>,
val randomTime: Float?,
val videoDuration: Float?
)
13 changes: 13 additions & 0 deletions app/src/main/java/com/github/libretube/api/obj/DeArrowThumbnail.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.libretube.api.obj

import kotlinx.serialization.Serializable

@Serializable
data class DeArrowThumbnail(
val UUID: String,
val locked: Boolean,
val original: Boolean,
val thumbnail: String? = null,
val timestamp: Float?,
val votes: Int
)
12 changes: 12 additions & 0 deletions app/src/main/java/com/github/libretube/api/obj/DeArrowTitle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.libretube.api.obj

import kotlinx.serialization.Serializable

@Serializable
data class DeArrowTitle(
val UUID: String,
val locked: Boolean,
val original: Boolean,
val title: String,
val votes: Int
)
2 changes: 1 addition & 1 deletion app/src/main/java/com/github/libretube/api/obj/Playlist.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ data class Playlist(
val uploaderUrl: String? = null,
val uploaderAvatar: String? = null,
val videos: Int = 0,
val relatedStreams: List<StreamItem> = emptyList()
var relatedStreams: List<StreamItem> = emptyList()
) {
fun toPlaylistBookmark(playlistId: String): PlaylistBookmark {
return PlaylistBookmark(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable

@Serializable
data class SearchResult(
val items: List<ContentItem> = emptyList(),
var items: List<ContentItem> = emptyList(),
val nextpage: String? = null,
val suggestion: String? = null,
val corrected: Boolean? = null
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/github/libretube/api/obj/StreamItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import kotlinx.serialization.Serializable
data class StreamItem(
val url: String? = null,
val type: String? = null,
val title: String? = null,
val thumbnail: String? = null,
var title: String? = null,
var thumbnail: String? = null,
val uploaderName: String? = null,
val uploaderUrl: String? = null,
val uploaderAvatar: String? = null,
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/github/libretube/api/obj/Streams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ data class Streams(
val dislikes: Long = 0,
val audioStreams: List<PipedStream> = emptyList(),
val videoStreams: List<PipedStream> = emptyList(),
val relatedStreams: List<StreamItem> = emptyList(),
var relatedStreams: List<StreamItem> = emptyList(),
val subtitles: List<Subtitle> = emptyList(),
val livestream: Boolean = false,
val proxyUrl: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ object PreferenceKeys {
const val PICTURE_IN_PICTURE = "picture_in_picture"
const val PLAYER_RESIZE_MODE = "player_resize_mode"
const val SB_SHOW_MARKERS = "sb_show_markers"
const val DEARROW = "dearrow"
const val ALTERNATIVE_PLAYER_LAYOUT = "alternative_player_layout"
const val USE_HLS_OVER_DASH = "use_hls"
const val QUEUE_AUTO_INSERT_RELATED = "queue_insert_related_videos"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.github.libretube.ui.adapters.SearchAdapter
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.extensions.setupSubscriptionButton
import com.github.libretube.util.deArrow
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -115,6 +116,8 @@ class ChannelFragment : Fragment() {
RetrofitInstance.api.getChannel(channelId!!)
} else {
RetrofitInstance.api.getChannelByName(channelName!!)
}.apply {
relatedStreams = relatedStreams.deArrow()
}
}
} catch (e: IOException) {
Expand Down Expand Up @@ -240,6 +243,8 @@ class ChannelFragment : Fragment() {
val response = try {
withContext(Dispatchers.IO) {
RetrofitInstance.api.getChannelTab(tab.data)
}.apply {
content = content.deArrow()
}
} catch (e: Exception) {
return@launch
Expand Down Expand Up @@ -270,7 +275,9 @@ class ChannelFragment : Fragment() {
repeatOnLifecycle(Lifecycle.State.CREATED) {
val response = try {
withContext(Dispatchers.IO) {
RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!)
RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!).apply {
relatedStreams = relatedStreams.deArrow()
}
}
} catch (e: IOException) {
_binding?.channelRefresh?.isRefreshing = false
Expand Down Expand Up @@ -301,6 +308,8 @@ class ChannelFragment : Fragment() {
val newContent = try {
withContext(Dispatchers.IO) {
RetrofitInstance.api.getChannelTab(tab.data, nextPage)
}.apply {
content = content.deArrow()
}
} catch (e: Exception) {
Log.e(TAG(), "Exception: $e")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter
import com.github.libretube.ui.adapters.PlaylistsAdapter
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.models.SubscriptionsViewModel
import com.github.libretube.util.deArrow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
Expand Down Expand Up @@ -116,7 +117,7 @@ class HomeFragment : Fragment() {
val region = LocaleHelper.getTrendingRegion(requireContext())
val trending = runCatching {
withContext(Dispatchers.IO) {
RetrofitInstance.api.getTrending(region).take(10)
RetrofitInstance.api.getTrending(region).deArrow().take(10)
}
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
val binding = _binding ?: return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.TextUtils
import com.github.libretube.util.TextUtils.toTimeInSeconds
import com.github.libretube.util.YoutubeHlsPlaylistParser
import com.github.libretube.util.deArrow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -690,7 +691,9 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {

lifecycleScope.launch(Dispatchers.IO) {
streams = try {
RetrofitInstance.api.getStreams(videoId)
RetrofitInstance.api.getStreams(videoId).apply {
relatedStreams = relatedStreams.deArrow()
}
} catch (e: IOException) {
context?.toastFromMainDispatcher(R.string.unknown_error, Toast.LENGTH_LONG)
return@launch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.hideKeyboard
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.ui.adapters.SearchAdapter
import com.github.libretube.util.deArrow
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -94,7 +95,9 @@ class SearchResultFragment : Fragment() {
view?.let { context?.hideKeyboard(it) }
val response = try {
withContext(Dispatchers.IO) {
RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
RetrofitInstance.api.getSearchResults(query, apiSearchFilter).apply {
items = items.deArrow()
}
}
} catch (e: IOException) {
println(e)
Expand Down Expand Up @@ -124,7 +127,9 @@ class SearchResultFragment : Fragment() {
query,
apiSearchFilter,
nextPage!!
)
).apply {
items = items.deArrow()
}
}
} catch (e: IOException) {
println(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.github.libretube.extensions.TAG
import com.github.libretube.helpers.LocaleHelper
import com.github.libretube.ui.activities.SettingsActivity
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.util.deArrow
import com.google.android.material.snackbar.Snackbar
import java.io.IOException
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -59,7 +60,7 @@ class TrendsFragment : Fragment() {
val response = try {
withContext(Dispatchers.IO) {
val region = LocaleHelper.getTrendingRegion(requireContext())
RetrofitInstance.api.getTrending(region)
RetrofitInstance.api.getTrending(region).deArrow()
}
} catch (e: IOException) {
println(e)
Expand Down
89 changes: 89 additions & 0 deletions app/src/main/java/com/github/libretube/util/DeArrowUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.github.libretube.util

import android.util.Log
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.ContentItem
import com.github.libretube.api.obj.DeArrowContent
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.extensions.toID
import com.github.libretube.helpers.PreferenceHelper
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement

object DeArrowUtil {
private fun extractTitleAndThumbnail(data: JsonElement): Pair<String?, String?> {
val content = try {
JsonHelper.json.decodeFromJsonElement<DeArrowContent>(data)
} catch (e: Exception) {
return null to null
}
val newTitle = content.titles.maxByOrNull { it.votes }?.title
val newThumbnail =
content.thumbnails.filter { it.thumbnail != null }.maxByOrNull { it.votes }
?.takeIf { !it.original }?.thumbnail
return newTitle to newThumbnail
}
Comment on lines +22 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array is presorted optimally (manually approved submissions above others even if less votes). Just take the first element.

However, you still need to make sure the top value is valid, as it will return submissions at -1 votes.

        val newTitle = content.titles.firstOrNull?.takeIf { it.votes >= 0 || it.locked }.title
        val newThumbnail =
            content.thumbnails.filter { it.thumbnail != null }.firstOrNull?.takeIf { (it.votes >= 0 || it.locked) && !it.original }?.thumbnail

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks!


/**
* Apply the new titles and thumbnails generated by DeArrow to the stream items
*/
suspend fun deArrowStreamItems(streamItems: List<StreamItem>): List<StreamItem> {
if (!PreferenceHelper.getBoolean(PreferenceKeys.DEARROW, false)) return streamItems

val videoIds = streamItems.mapNotNull { it.url?.toID() }.joinToString(",")
val response = try {
RetrofitInstance.api.getDeArrowContent(videoIds)
} catch (e: Exception) {
Log.e(this::class.java.name, e.toString())
return streamItems
}
for ((videoId, data) in response.entries) {
val (newTitle, newThumbnail) = extractTitleAndThumbnail(data)
val streamItem = streamItems.firstOrNull { it.url?.toID() == videoId }
newTitle?.let { streamItem?.title = newTitle }
newThumbnail?.let { streamItem?.thumbnail = newThumbnail }
}
return streamItems
}

/**
* Apply the new titles and thumbnails generated by DeArrow to the stream items
*/
suspend fun deArrowContentItems(contentItems: List<ContentItem>): List<ContentItem> {
if (!PreferenceHelper.getBoolean(PreferenceKeys.DEARROW, false)) return contentItems

val videoIds = contentItems.filter { it.type == "stream" }
.joinToString(",") { it.url.toID() }
if (videoIds.isEmpty()) return contentItems

val response = try {
RetrofitInstance.api.getDeArrowContent(videoIds)
} catch (e: Exception) {
Log.e(this::class.java.name, e.toString())
return contentItems
}
for ((videoId, data) in response.entries) {
val (newTitle, newThumbnail) = extractTitleAndThumbnail(data)
val contentItem = contentItems.firstOrNull { it.url.toID() == videoId }
newTitle?.let { contentItem?.title = newTitle }
newThumbnail?.let { contentItem?.thumbnail = newThumbnail }
}
return contentItems
}
}

/**
* If enabled in the preferences, this overrides the video's thumbnail and title with the one
* provided by the DeArrow project
*/
@JvmName("deArrowStreamItems")
suspend fun List<StreamItem>.deArrow() = DeArrowUtil.deArrowStreamItems(this)

/**
* If enabled in the preferences, this overrides the video's thumbnail and title with the one
* provided by the DeArrow project
*/
@JvmName("deArrowContentItems")
suspend fun List<ContentItem>.deArrow() = DeArrowUtil.deArrowContentItems(this)
Loading