upload a video to bluesky via the simple method. the video is not pre-processed and thus is not available for a short while after making the post. A better approach is to pre-process the video - see here: https://tangled.sh/strings/samuel.bsky.team/3lw743ejno722
video-upload-simple.ts edited
71 lines 2.0 kB view raw
1// NOTE: this uses Deno 2 3import { AppBskyEmbedVideo, AtpAgent } from "npm:@atproto/api"; 4 5const userAgent = new AtpAgent({ 6 service: prompt("Service URL (default: https://bsky.social):") || 7 "https://bsky.social", 8}); 9 10await userAgent.login({ 11 identifier: prompt("Handle:")!, 12 password: prompt("Password:")!, 13}); 14 15console.log(`Logged in as ${userAgent.session?.handle}`); 16 17const videoPath = prompt("Video file (.mp4):")!; 18 19const file = await Deno.open(videoPath); 20const { size } = await file.stat(); 21 22// optional: print upload progress 23let bytesUploaded = 0; 24const progressTrackingStream = new TransformStream({ 25 transform(chunk, controller) { 26 controller.enqueue(chunk); 27 bytesUploaded += chunk.byteLength; 28 console.log( 29 "upload progress:", 30 Math.trunc(bytesUploaded / size * 100) + "%", 31 ); 32 }, 33 flush() { 34 console.log("upload complete ✨"); 35 }, 36}); 37 38const { data } = await userAgent.com.atproto.repo.uploadBlob( 39 // @ts-expect-error - expecting an Uint8Array, but a ReadableStream is fine 40 file.readable.pipeThrough(progressTrackingStream), 41); 42 43console.log("video uploaded, posting..."); 44 45await userAgent.post({ 46 text: "This post should have a video attached", 47 langs: ["en"], 48 embed: { 49 $type: "app.bsky.embed.video", 50 video: data.blob, 51 aspectRatio: await getAspectRatio(videoPath), 52 } satisfies AppBskyEmbedVideo.Main, 53}); 54 55console.log("done ✨ (video will take a little bit to process)"); 56 57// bonus: get aspect ratio using ffprobe 58// in the browser, you can just put the video uri in a <video> element 59// and measure the dimensions once it loads. in React Native, the image picker 60// will give you the dimensions directly 61 62import { ffprobe } from "https://deno.land/x/fast_forward@0.1.6/ffprobe.ts"; 63 64async function getAspectRatio(fileName: string) { 65 const { streams } = await ffprobe(fileName, {}); 66 const videoSteam = streams.find((stream) => stream.codec_type === "video"); 67 return { 68 width: videoSteam.width, 69 height: videoSteam.height, 70 }; 71}