From f1bb73437ed57839c44b1dd2c2f3094b21f14696 Mon Sep 17 00:00:00 2001 From: dwot <68145+dwot@users.noreply.github.com> Date: Sat, 30 Aug 2025 03:43:01 -0400 Subject: [PATCH] Add mp4box library and add aspect ratio calculation and alt tag to video uploads to bluesky --- .../integrations/social/bluesky.provider.ts | 75 +++++++++++++++++-- package.json | 1 + 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts index 0e3f9e5cc..c44a38cdd 100644 --- a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts @@ -26,6 +26,58 @@ import { Plug } from '@gitroom/helpers/decorators/plug.decorator'; import { timer } from '@gitroom/helpers/utils/timer'; import axios from 'axios'; import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; +import * as MP4BoxNS from 'mp4box'; + +type MP4BoxBuffer = ArrayBuffer & { fileStart: number }; + +async function getMp4PixelSizeFromBuffer(buf: Buffer): Promise<{ width: number; height: number }> { + try { + const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer; + const mp4buf = ab as MP4BoxBuffer; + mp4buf.fileStart = 0; + + // Handle ESM/CJS interop: mp4box can appear as default export or namespace + const MP4: any = (MP4BoxNS as any)?.createFile + ? MP4BoxNS + : (MP4BoxNS as any)?.default?.createFile + ? (MP4BoxNS as any).default + : null; + + if (!MP4 || typeof MP4.createFile !== 'function') { + throw new Error('mp4box import did not expose createFile()'); + } + + const mp4 = MP4.createFile(); + + return await new Promise<{ width: number; height: number }>((resolve, reject) => { + mp4.onError = (e: unknown) => { + console.error('ARCheck - onError', e); + reject(new Error(String(e))); + }; + mp4.onReady = (info: any) => { + try { + const track = info?.tracks?.find((t: any) => t?.video); + const w = Number(track?.video?.width || 0); + const h = Number(track?.video?.height || 0); + if (!w || !h) return reject(new Error('No video track dimensions found')); + + const rotation = Number(track?.video?.rotation ?? 0); + const width = rotation === 90 || rotation === 270 ? h : w; + const height = rotation === 90 || rotation === 270 ? w : h; + + resolve({ width, height }); + } catch (e) { + reject(e); + } + }; + mp4.appendBuffer(mp4buf); + mp4.flush(); + }); + } catch (err) { + console.error('getMp4PixelSizeFromBuffer - FAILED:', err); + throw err; // rethrow so the caller surfaces the real error + } +} async function reduceImageBySize(url: string, maxSizeKB = 976) { try { @@ -62,7 +114,8 @@ async function reduceImageBySize(url: string, maxSizeKB = 976) { async function uploadVideo( agent: AtpAgent, - videoPath: string + videoPath: string, + videoAlt: string ): Promise { const { data: serviceAuth } = await agent.com.atproto.server.getServiceAuth({ aud: `did:web:${agent.dispatchUrl.host}`, @@ -135,10 +188,20 @@ async function uploadVideo( console.log('posting video...'); - return { - $type: 'app.bsky.embed.video', - video: blob, - } satisfies AppBskyEmbedVideo.Main; + let width = 0, height = 0; + try { + ({ width, height } = await getMp4PixelSizeFromBuffer(video.video)); + } catch (e) { + console.error('[BlueSky] aspect probe failed:', e); + throw e; + } + + return { + $type: 'app.bsky.embed.video', + video: blob, + aspectRatio: { width, height }, + alt: videoAlt, + } satisfies AppBskyEmbedVideo.Main; } export class BlueskyProvider extends SocialAbstract implements SocialProvider { @@ -281,7 +344,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { // Upload videos (only one video per post is supported by Bluesky) let videoEmbed: AppBskyEmbedVideo.Main | null = null; if (videoMedia.length > 0) { - videoEmbed = await uploadVideo(agent, videoMedia[0].path); + videoEmbed = await uploadVideo(agent, videoMedia[0].path, videoMedia[0].alt); } const rt = new RichText({ diff --git a/package.json b/package.json index 5bed657ff..e77e1ab47 100644 --- a/package.json +++ b/package.json @@ -163,6 +163,7 @@ "md5": "^2.3.0", "mime": "^3.0.0", "mime-types": "^2.1.35", + "mp4box": "^2.1.0", "multer": "^1.4.5-lts.1", "music-metadata": "^7.14.0", "nestjs-command": "^3.1.4",