composition time formatting
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user