composition time formatting

This commit is contained in:
vineyardbovines
2026-04-16 13:14:19 -04:00
parent 578e871872
commit 47706b95b4
5 changed files with 77 additions and 12 deletions
+14
View File
@@ -453,6 +453,7 @@ describe('parseEmbedPlayerFromUrl', () => {
'https://bandcamp.com',
'https://static.klipy.com/ii/abc123/73/ac/someFile.gif?hh=200&ww=300',
'https://static.klipy.com/ii/abc123/73/ac/someFile.gif?hh=200&ww=300&mp4=videoSlugMp4&webm=videoSlugWebm',
'https://static.klipy.com/ii/abc123/73/ac/someFile.gif?hh=200',
'https://static.klipy.com/ii/abc123/73/ac/someFile.gif',
'https://static.klipy.com/other/path.gif?hh=200&ww=300',
@@ -853,6 +854,19 @@ describe('parseEmbedPlayerFromUrl', () => {
undefined,
undefined,
{
type: 'klipy_gif',
source: 'klipy',
isGif: true,
hideDetails: true,
playerUri: 'https://k.gifs.bsky.app/ii/abc123/73/ac/someFile.gif',
dimensions: {
width: 300,
height: 200,
},
},
// With video slug params — on native (test env), keeps gif filename,
// strips mp4/webm params. On web, would swap to video filename.
{
type: 'klipy_gif',
source: 'klipy',
+28 -1
View File
@@ -190,7 +190,26 @@ export async function resolveGif(
agent: BskyAgent,
gif: Gif,
): Promise<ResolvedExternalLink> {
const uri = `${gif.media_formats.gif.url}?hh=${gif.media_formats.gif.dims[1]}&ww=${gif.media_formats.gif.dims[0]}`
const gifUrl = gif.media_formats.gif.url
const params = new URLSearchParams()
params.set('hh', String(gif.media_formats.gif.dims[1]))
params.set('ww', String(gif.media_formats.gif.dims[0]))
// For Klipy GIFs, embed video format slugs so parseKlipyGif can
// swap to the right format per platform at render time. Klipy uses
// different filename slugs per format (unlike Tenor where format is
// encoded in the URL ID), so this info must travel with the URL.
try {
const url = new URL(gifUrl)
if (url.hostname === 'static.klipy.com') {
const mp4Slug = getFileSlug(gif.media_formats.mp4?.url)
const webmSlug = getFileSlug(gif.media_formats.webm?.url)
if (mp4Slug) params.set('mp4', mp4Slug)
if (webmSlug) params.set('webm', webmSlug)
}
} catch {}
const uri = `${gifUrl}?${params.toString()}`
const altText = gif.content_description || gif.title
return {
type: 'external',
@@ -201,6 +220,14 @@ export async function resolveGif(
}
}
function getFileSlug(url: string | undefined): string | undefined {
if (!url) return undefined
const filename = url.split('/').pop()
if (!filename) return undefined
const dotIndex = filename.lastIndexOf('.')
return dotIndex > 0 ? filename.slice(0, dotIndex) : undefined
}
async function resolveExternal(
agent: BskyAgent,
uri: string,
+21 -4
View File
@@ -683,14 +683,31 @@ export function parseKlipyGif(urlp: URL):
return {success: false}
}
// Use the base URL without dimension params as the player URI,
// routed through the bsky KLIPY proxy (k.gifs.bsky.app). Mirrors
// Tenor's t.gifs.bsky.app rewrite, but on a separate hostname so
// the two upstreams can be routed independently.
const playerUrl = new URL(urlp.href)
playerUrl.hostname = 'k.gifs.bsky.app'
// On web, swap the gif filename for a video format so the <video>
// element can play it. Klipy uses different filename slugs per
// format (unlike Tenor's ID-based scheme), so the slugs are
// embedded as query params at composition time by resolveGif().
if (IS_WEB) {
const webmSlug = playerUrl.searchParams.get('webm')
const mp4Slug = playerUrl.searchParams.get('mp4')
const slug = IS_WEB_SAFARI ? mp4Slug : webmSlug
const ext = IS_WEB_SAFARI ? 'mp4' : 'webm'
if (slug) {
const parts = playerUrl.pathname.split('/')
parts[parts.length - 1] = `${slug}.${ext}`
playerUrl.pathname = parts.join('/')
}
}
// Strip all metadata params — only the path matters for the CDN
playerUrl.searchParams.delete('hh')
playerUrl.searchParams.delete('ww')
playerUrl.searchParams.delete('mp4')
playerUrl.searchParams.delete('webm')
return {
success: true,
+12 -7
View File
@@ -143,7 +143,8 @@ export type Gif = {
/**
* A dictionary with a content format as the key and a Media Object as the value.
*/
media_formats: Record<ContentFormats, MediaObject>
media_formats: Record<BaseContentFormats, MediaObject> &
Partial<Record<VideoContentFormats, MediaObject>>
/**
* An array of tags for the post
*/
@@ -198,16 +199,20 @@ type MediaObject = {
size: number
}
type ContentFormats =
type BaseContentFormats =
| 'preview'
| 'gif'
// | 'mediumgif'
| 'tinygif'
// | 'nanogif'
// | 'mp4'
// | 'loopedmp4'
// | 'tinymp4'
// | 'nanomp4'
// | 'webm'
type VideoContentFormats =
| 'mp4'
// | 'loopedmp4'
// | 'tinymp4'
// | 'nanomp4'
| 'webm'
// | 'tinywebm'
// | 'nanowebm'
type ContentFormats = BaseContentFormats | VideoContentFormats
@@ -397,6 +397,8 @@ function parseGifFromUrl(
url.searchParams.delete('ww')
url.searchParams.delete('hh')
url.searchParams.delete('alt')
url.searchParams.delete('mp4')
url.searchParams.delete('webm')
return {url: url.toString(), width, height, alt}
} catch {