diff --git a/src/org/kde/kdeconnect/Helpers/VideoUrlsHelper.kt b/src/org/kde/kdeconnect/Helpers/VideoUrlsHelper.kt index b9a29ddc..69ae8013 100644 --- a/src/org/kde/kdeconnect/Helpers/VideoUrlsHelper.kt +++ b/src/org/kde/kdeconnect/Helpers/VideoUrlsHelper.kt @@ -13,6 +13,9 @@ object VideoUrlsHelper { private const val MINUTES_IN_HOUR = 60 private const val SECONDS_IN_HOUR = SECONDS_IN_MINUTE * MINUTES_IN_HOUR + /** PeerTube uses a Flickr Base58 encoded short UUID (alphanumeric, but 0, O, I, and l are excluded) with a length of 22 characters **/ + private val peerTubePathPattern = Regex("^/w/[1-9a-km-zA-HJ-NP-Z]{22}(\\?.+)?$") + @Throws(MalformedURLException::class) fun formatUriWithSeek(address: String, position: Long): URL { val positionSeconds = position / 1000 // milliseconds to seconds @@ -33,7 +36,10 @@ object VideoUrlsHelper { url.editParameter("start", Regex("\\d+")) { "$positionSeconds" } } host.contains("twitch.tv") -> { - url.editParameter("t", Regex("(\\d+[hH])?(\\d+[mM])?\\d+[sS]")) { positionSeconds.formatTimestampHMS() } + url.editParameter("t", Regex("(\\d+[hH])?(\\d+[mM])?\\d+[sS]")) { formatTimestampHMS(positionSeconds) } + } + url.path.matches(peerTubePathPattern) -> { + url.editParameter("start", Regex("(\\d+[hH])?(\\d+[mM])?\\d+[sS]")) { formatTimestampHMS(positionSeconds) } } else -> url } @@ -62,13 +68,16 @@ object VideoUrlsHelper { return URL("${urlBase}?${modifiedUrlQuery}") // -> "https://www.youtube.com/watch?v=ovX5G0O5ZvA&t=14" } - /** Formats timestamp to e.g. 01h02m34s */ - private fun Long.formatTimestampHMS(): String { - if (this == 0L) return "0s" + /** + * @param timestamp in seconds + * @return timestamp formatted as e.g. "01h02m34s" + * */ + private fun formatTimestampHMS(timestamp: Long): String { + if (timestamp == 0L) return "0s" - val seconds: Long = this % SECONDS_IN_MINUTE - val minutes: Long = (this / SECONDS_IN_MINUTE) % MINUTES_IN_HOUR - val hours: Long = this / SECONDS_IN_HOUR + val seconds: Long = timestamp % SECONDS_IN_MINUTE + val minutes: Long = (timestamp / SECONDS_IN_MINUTE) % MINUTES_IN_HOUR + val hours: Long = timestamp / SECONDS_IN_HOUR fun pad(s: String) = s.padStart(3, '0') val hoursText = if (hours > 0) pad("${hours}h") else "" diff --git a/tests/org/kde/kdeconnect/Helpers/VideoUrlsHelperTest.kt b/tests/org/kde/kdeconnect/Helpers/VideoUrlsHelperTest.kt index deef9e31..92311828 100644 --- a/tests/org/kde/kdeconnect/Helpers/VideoUrlsHelperTest.kt +++ b/tests/org/kde/kdeconnect/Helpers/VideoUrlsHelperTest.kt @@ -72,4 +72,31 @@ class VideoUrlsHelperTest { val expected = "https://example.org/cool_video.mp4" Assert.assertEquals(expected, formatted.toString()) } + + @Test + fun checkPeerTubeURL() { + val validUrls = mapOf( + "https://video.blender.org/w/472h2s5srBFmAThiZVw96R?start=01m27s" to "https://video.blender.org/w/472h2s5srBFmAThiZVw96R?start=01m30s", + "https://video.blender.org/w/mDyZP2TrdjjjNRMoVUgPM2?start=01m27s" to "https://video.blender.org/w/mDyZP2TrdjjjNRMoVUgPM2?start=01m30s", + "https://video.blender.org/w/evhMcVhvK6VeAKJwCSuHSe?start=01m27s" to "https://video.blender.org/w/evhMcVhvK6VeAKJwCSuHSe?start=01m30s", + "https://video.blender.org/w/54tzKpEguEEu26Hi8Lcpna?start=01m27s" to "https://video.blender.org/w/54tzKpEguEEu26Hi8Lcpna?start=01m30s", + "https://video.blender.org/w/o5VtGNQaNpFNNHiJbLy4eM?start=01m27s" to "https://video.blender.org/w/o5VtGNQaNpFNNHiJbLy4eM?start=01m30s", + ) + for ((from, to) in validUrls) { + val formatted = VideoUrlsHelper.formatUriWithSeek(from, 90_000L) + Assert.assertEquals(to, formatted.toString()) + } + val invalidUrls = listOf( + "https://video.blender.org/w/472h2s5srBFmAOhiZVw96R?start=01m27s", // invalid character (O) + "https://video.blender.org/w/mDyZP2TrdjjjNIMoVUgPM2?start=01m27s", // invalid character (I) + "https://video.blender.org/w/evhMcVhvK6VeAlJwCSuHSe?start=01m27s", // invalid character (l) + "https://video.blender.org/w/54tzKpEguEEu20Hi8Lcpna?start=01m27s", // invalid character (0) + "https://video.blender.org/w/o5VtGNQaNpFNHiJbLy4eM?start=01m27s", // invalid length (21) + "https://video.blender.org/w/hb43bRmBzNpHd4sW74Y4cyAB?start=01m27s", // invalid length (23) + ) + for (url in invalidUrls) { + val formatted = VideoUrlsHelper.formatUriWithSeek(url, 90_000L) + Assert.assertEquals(url, formatted.toString()) // should not modify the URL + } + } } \ No newline at end of file