refactor: simplify and improve playlist export logic

This commit is contained in:
Bnyro 2024-12-05 21:41:35 +01:00
parent e4bbd51ef0
commit b062c6165e
2 changed files with 54 additions and 45 deletions

View File

@ -14,10 +14,7 @@ import com.github.libretube.extensions.parallelMap
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.helpers.ProxyHelper 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.obj.PipedImportPlaylist
import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL
import com.github.libretube.util.deArrow import com.github.libretube.util.deArrow
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -201,35 +198,11 @@ object PlaylistsHelper {
} }
} }
suspend fun exportPipedPlaylists(): List<PipedImportPlaylist> = withContext(Dispatchers.IO) { suspend fun getAllPlaylistsWithVideos(playlistIds: List<String>? = null): List<Playlist> =
getPlaylists()
.map { async { getPlaylist(it.id!!) } }
.awaitAll()
.map {
val videos = it.relatedStreams.map { item ->
"$YOUTUBE_FRONTEND_URL/watch?v=${item.url!!.toID()}"
}
PipedImportPlaylist(it.name, "playlist", "private", videos)
}
}
suspend fun exportFreeTubePlaylists(): List<FreeTubeImportPlaylist> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
getPlaylists() (playlistIds ?: getPlaylists().map { it.id!! })
.map { async { getPlaylist(it.id!!) } } .map { async { getPlaylist(it) } }
.awaitAll() .awaitAll()
.map { playlist ->
val videos = playlist.relatedStreams.map { videoInfo ->
FreeTubeVideo(
videoId = videoInfo.url.orEmpty().toID(),
title = videoInfo.title.orEmpty(),
author = videoInfo.uploaderName.orEmpty(),
authorId = videoInfo.uploaderUrl.orEmpty().toID(),
lengthSeconds = videoInfo.duration ?: 0L
)
}
FreeTubeImportPlaylist(playlist.name.orEmpty(), videos)
}
} }
suspend fun clonePlaylist(playlistId: String): String? { suspend fun clonePlaylist(playlistId: String): String? {

View File

@ -15,8 +15,10 @@ import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.db.obj.WatchHistoryItem import com.github.libretube.db.obj.WatchHistoryItem
import com.github.libretube.enums.ImportFormat import com.github.libretube.enums.ImportFormat
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toastFromMainDispatcher import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.obj.FreeTubeImportPlaylist import com.github.libretube.obj.FreeTubeImportPlaylist
import com.github.libretube.obj.FreeTubeVideo
import com.github.libretube.obj.FreetubeSubscription import com.github.libretube.obj.FreetubeSubscription
import com.github.libretube.obj.FreetubeSubscriptions import com.github.libretube.obj.FreetubeSubscriptions
import com.github.libretube.obj.NewPipeSubscription import com.github.libretube.obj.NewPipeSubscription
@ -24,7 +26,8 @@ import com.github.libretube.obj.NewPipeSubscriptions
import com.github.libretube.obj.PipedImportPlaylist import com.github.libretube.obj.PipedImportPlaylist
import com.github.libretube.obj.PipedPlaylistFile import com.github.libretube.obj.PipedPlaylistFile
import com.github.libretube.obj.YouTubeWatchHistoryFileItem import com.github.libretube.obj.YouTubeWatchHistoryFileItem
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL
import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_SHORT_URL
import com.github.libretube.util.TextUtils import com.github.libretube.util.TextUtils
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
@ -34,6 +37,7 @@ import java.util.stream.Collectors
object ImportHelper { object ImportHelper {
private const val IMPORT_THUMBNAIL_QUALITY = "mqdefault" private const val IMPORT_THUMBNAIL_QUALITY = "mqdefault"
private const val VIDEO_ID_LENGTH = 11
/** /**
* Import subscriptions by a file uri * Import subscriptions by a file uri
@ -70,7 +74,7 @@ object ImportHelper {
JsonHelper.json.decodeFromStream<NewPipeSubscriptions>(it) JsonHelper.json.decodeFromStream<NewPipeSubscriptions>(it)
} }
subscriptions?.subscriptions.orEmpty().map { subscriptions?.subscriptions.orEmpty().map {
it.url.replace("${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/", "") it.url.replace("$YOUTUBE_FRONTEND_URL/channel/", "")
} }
} }
@ -79,7 +83,7 @@ object ImportHelper {
JsonHelper.json.decodeFromStream<FreetubeSubscriptions>(it) JsonHelper.json.decodeFromStream<FreetubeSubscriptions>(it)
} }
subscriptions?.subscriptions.orEmpty().map { subscriptions?.subscriptions.orEmpty().map {
it.url.replace("${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/", "") it.url.replace("$YOUTUBE_FRONTEND_URL/channel/", "")
} }
} }
@ -114,7 +118,7 @@ object ImportHelper {
when (importFormat) { when (importFormat) {
ImportFormat.NEWPIPE -> { ImportFormat.NEWPIPE -> {
val newPipeChannels = subs.map { val newPipeChannels = subs.map {
NewPipeSubscription(it.name, 0, "${ShareDialog.YOUTUBE_FRONTEND_URL}${it.url}") NewPipeSubscription(it.name, 0, "$YOUTUBE_FRONTEND_URL${it.url}")
} }
val newPipeSubscriptions = NewPipeSubscriptions(subscriptions = newPipeChannels) val newPipeSubscriptions = NewPipeSubscriptions(subscriptions = newPipeChannels)
activity.contentResolver.openOutputStream(uri)?.use { activity.contentResolver.openOutputStream(uri)?.use {
@ -127,7 +131,7 @@ object ImportHelper {
FreetubeSubscription( FreetubeSubscription(
it.name, it.name,
"", "",
"${ShareDialog.YOUTUBE_FRONTEND_URL}${it.url}" "$YOUTUBE_FRONTEND_URL${it.url}"
) )
} }
val freeTubeSubscriptions = FreetubeSubscriptions(subscriptions = freeTubeChannels) val freeTubeSubscriptions = FreetubeSubscriptions(subscriptions = freeTubeChannels)
@ -158,7 +162,7 @@ object ImportHelper {
// convert the YouTube URLs to videoIds // convert the YouTube URLs to videoIds
importPlaylists.forEach { playlist -> importPlaylists.forEach { playlist ->
playlist.videos = playlist.videos.map { it.takeLast(11) } playlist.videos = playlist.videos.map { it.takeLast(VIDEO_ID_LENGTH) }
} }
} }
@ -225,7 +229,7 @@ object ImportHelper {
// convert the YouTube URLs to videoIds // convert the YouTube URLs to videoIds
importPlaylists.forEach { importPlaylist -> importPlaylists.forEach { importPlaylist ->
importPlaylist.videos = importPlaylist.videos.map { it.takeLast(11) } importPlaylist.videos = importPlaylist.videos.map { it.takeLast(VIDEO_ID_LENGTH) }
} }
} }
@ -236,7 +240,7 @@ object ImportHelper {
playlist.videos = inputStream.bufferedReader().readLines() playlist.videos = inputStream.bufferedReader().readLines()
.flatMap { it.split(",") } .flatMap { it.split(",") }
.mapNotNull { videoUrlOrId -> .mapNotNull { videoUrlOrId ->
if (videoUrlOrId.length == 11) { if (videoUrlOrId.length == VIDEO_ID_LENGTH) {
videoUrlOrId videoUrlOrId
} else { } else {
TextUtils.getVideoIdFromUri(videoUrlOrId.toUri()) TextUtils.getVideoIdFromUri(videoUrlOrId.toUri())
@ -272,11 +276,22 @@ object ImportHelper {
* Export Playlists * Export Playlists
*/ */
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
suspend fun exportPlaylists(activity: Activity, uri: Uri, importFormat: ImportFormat) { suspend fun exportPlaylists(
activity: Activity,
uri: Uri,
importFormat: ImportFormat,
selectedPlaylistIds: List<String>? = null
) {
val playlists = PlaylistsHelper.getAllPlaylistsWithVideos(selectedPlaylistIds)
when (importFormat) { when (importFormat) {
ImportFormat.PIPED -> { ImportFormat.PIPED -> {
val playlists = PlaylistsHelper.exportPipedPlaylists() val playlistFile = PipedPlaylistFile(playlists = playlists.map {
val playlistFile = PipedPlaylistFile(playlists = playlists) val videos = it.relatedStreams.map { item ->
"$YOUTUBE_FRONTEND_URL/watch?v=${item.url!!.toID()}"
}
PipedImportPlaylist(it.name, "playlist", "private", videos)
})
activity.contentResolver.openOutputStream(uri)?.use { activity.contentResolver.openOutputStream(uri)?.use {
JsonHelper.json.encodeToStream(playlistFile, it) JsonHelper.json.encodeToStream(playlistFile, it)
@ -285,17 +300,38 @@ object ImportHelper {
} }
ImportFormat.FREETUBE -> { ImportFormat.FREETUBE -> {
val playlists = PlaylistsHelper.exportFreeTubePlaylists() val freeTubeExportDb = playlists.map { playlist ->
val videos = playlist.relatedStreams.map { videoInfo ->
val freeTubeExportDb = playlists.joinToString("\n") { playlist -> FreeTubeVideo(
videoId = videoInfo.url.orEmpty().toID(),
title = videoInfo.title.orEmpty(),
author = videoInfo.uploaderName.orEmpty(),
authorId = videoInfo.uploaderUrl.orEmpty().toID(),
lengthSeconds = videoInfo.duration ?: 0L
)
}
FreeTubeImportPlaylist(playlist.name.orEmpty(), videos)
}.joinToString("\n") { playlist ->
JsonHelper.json.encodeToString(playlist) JsonHelper.json.encodeToString(playlist)
} }
activity.contentResolver.openOutputStream(uri)?.use { activity.contentResolver.openOutputStream(uri)?.use {
it.write(freeTubeExportDb.toByteArray()) it.write(freeTubeExportDb.toByteArray())
} }
activity.toastFromMainDispatcher(R.string.exportsuccess) activity.toastFromMainDispatcher(R.string.exportsuccess)
} }
ImportFormat.URLSORIDS -> {
val urlListExport = playlists
.flatMap { it.relatedStreams }
.joinToString("\n") { YOUTUBE_SHORT_URL + "/watch?v=" + it.url!!.toID() }
activity.contentResolver.openOutputStream(uri)?.use {
it.write(urlListExport.toByteArray())
}
activity.toastFromMainDispatcher(R.string.exportsuccess)
}
else -> Unit else -> Unit
} }
} }
@ -311,7 +347,7 @@ object ImportHelper {
.filter { it.activityControls.contains("YouTube watch history") && it.subtitles.isNotEmpty() && it.titleUrl.isNotEmpty() } .filter { it.activityControls.contains("YouTube watch history") && it.subtitles.isNotEmpty() && it.titleUrl.isNotEmpty() }
.reversed() .reversed()
.map { .map {
val videoId = it.titleUrl.substring(it.titleUrl.length - 11) val videoId = it.titleUrl.takeLast(VIDEO_ID_LENGTH)
WatchHistoryItem( WatchHistoryItem(
videoId = videoId, videoId = videoId,