Created
November 10, 2024 13:33
-
-
Save mozzius/c3cd72948265d161f339f0ab66fe5e0c to your computer and use it in GitHub Desktop.
please add gifs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
AppBskyEmbedVideo, | |
AppBskyVideoDefs, | |
AtpAgent, | |
BlobRef, | |
} from "npm:@atproto/api"; | |
const userAgent = new AtpAgent({ | |
service: prompt("Service URL (default: https://bsky.social):") || | |
"https://bsky.social", | |
}); | |
await userAgent.login({ | |
identifier: prompt("Handle:")!, | |
password: prompt("Password:")!, | |
}); | |
console.log(`Logged in as ${userAgent.session?.handle}`); | |
const videoPath = prompt("GIF file (.gif):")!; | |
const { data: serviceAuth } = await userAgent.com.atproto.server.getServiceAuth( | |
{ | |
aud: `did:web:${userAgent.dispatchUrl.host}`, | |
lxm: "com.atproto.repo.uploadBlob", | |
exp: Date.now() / 1000 + 60 * 30, // 30 minutes | |
}, | |
); | |
const token = serviceAuth.token; | |
const file = await Deno.open(videoPath); | |
const { size } = await file.stat(); | |
// optional: print upload progress | |
let bytesUploaded = 0; | |
const progressTrackingStream = new TransformStream({ | |
transform(chunk, controller) { | |
controller.enqueue(chunk); | |
bytesUploaded += chunk.byteLength; | |
console.log( | |
"upload progress:", | |
Math.trunc(bytesUploaded / size * 100) + "%", | |
); | |
}, | |
flush() { | |
console.log("upload complete ✨"); | |
}, | |
}); | |
const uploadUrl = new URL( | |
"https://video.bsky.app/xrpc/app.bsky.video.uploadVideo", | |
); | |
uploadUrl.searchParams.append("did", userAgent.session!.did); | |
uploadUrl.searchParams.append("name", videoPath.split("/").pop()!); | |
const uploadResponse = await fetch(uploadUrl, { | |
method: "POST", | |
headers: { | |
Authorization: `Bearer ${token}`, | |
"Content-Type": "image/gif", | |
"Content-Length": String(size), | |
}, | |
body: file.readable.pipeThrough(progressTrackingStream), | |
}); | |
const jobStatus = (await uploadResponse.json()) as AppBskyVideoDefs.JobStatus; | |
if (!jobStatus.jobId) { | |
console.log(jobStatus); | |
throw new Error("JobId not found"); | |
} | |
console.log("JobId:", jobStatus.jobId); | |
let blob: BlobRef | undefined = jobStatus.blob; | |
const videoAgent = new AtpAgent({ service: "https://video.bsky.app" }); | |
while (!blob) { | |
const { data: status } = await videoAgent.app.bsky.video.getJobStatus( | |
{ jobId: jobStatus.jobId }, | |
); | |
console.log( | |
"Status:", | |
status.jobStatus.state, | |
status.jobStatus.progress || "", | |
); | |
if (status.jobStatus.blob) { | |
blob = status.jobStatus.blob; | |
} | |
// wait a second | |
await new Promise((resolve) => setTimeout(resolve, 1000)); | |
} | |
console.log("posting..."); | |
await userAgent.post({ | |
text: "gif", | |
langs: ["en"], | |
embed: { | |
$type: "app.bsky.embed.video", | |
video: blob, | |
aspectRatio: await getAspectRatio(videoPath), | |
} satisfies AppBskyEmbedVideo.Main, | |
}); | |
console.log("done ✨"); | |
// bonus: get aspect ratio using ffprobe | |
// in the browser, you can just put the video uri in a <video> element | |
// and measure the dimensions once it loads. in React Native, the image picker | |
// will give you the dimensions directly | |
import { ffprobe } from "https://deno.land/x/[email protected]/ffprobe.ts"; | |
async function getAspectRatio(fileName: string) { | |
const { streams } = await ffprobe(fileName, {}); | |
const videoSteam = streams.find((stream) => stream.codec_type === "video"); | |
return { | |
width: videoSteam.width, | |
height: videoSteam.height, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment