A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.

fix: improve video playback with thumbnails and medium bitrate variants

jack 27c97e4c 7ce2091f

+24 -4
+24 -4
src/index.ts
··· 406 406 407 407 const images: ImageEmbed[] = []; 408 408 let videoBlob: BlobRef | null = null; 409 + let videoThumbnailBlob: BlobRef | null = null; 409 410 let videoAspectRatio: AspectRatio | undefined; 410 411 const mediaEntities = tweet.extended_entities?.media || tweet.entities?.media || []; 411 412 const mediaLinksToRemove: string[] = []; ··· 436 437 const variants = media.video_info?.variants || []; 437 438 const mp4s = variants 438 439 .filter((v) => v.content_type === 'video/mp4') 439 - .sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0)); 440 + .sort((a, b) => (a.bitrate || 0) - (b.bitrate || 0)); // Sort ASCENDING to pick smaller variants 440 441 441 - if (mp4s.length > 0 && mp4s[0]) { 442 - const videoUrl = mp4s[0].url; 442 + if (mp4s.length > 0) { 443 + // Pick a medium variant if available, otherwise the smallest 444 + const variantIndex = mp4s.length > 1 ? 1 : 0; 445 + const variant = mp4s[variantIndex]; 446 + if (!variant) continue; 447 + 448 + const videoUrl = variant.url; 443 449 try { 444 450 const { buffer, mimeType } = await downloadMedia(videoUrl); 445 451 if (buffer.length > 95 * 1024 * 1024) { ··· 448 454 if (!text.includes(tweetUrl)) text += `\n${tweetUrl}`; 449 455 continue; 450 456 } 457 + 458 + // 1. Upload Video 451 459 const blob = await uploadToBluesky(agent, buffer, mimeType); 452 460 videoBlob = blob; 453 461 videoAspectRatio = aspectRatio; 454 - break; // Prioritize video and stop looking for other media 462 + 463 + // 2. Try to upload thumbnail 464 + if (media.media_url_https) { 465 + try { 466 + const thumb = await downloadMedia(media.media_url_https); 467 + videoThumbnailBlob = await uploadToBluesky(agent, thumb.buffer, thumb.mimeType); 468 + } catch (e) { 469 + console.warn('Failed to upload video thumbnail'); 470 + } 471 + } 472 + 473 + break; // Prioritize video 455 474 } catch (err) { 456 475 console.warn(`Failed to upload video ${videoUrl}, linking to tweet instead:`, (err as Error).message); 457 476 const tweetUrl = `https://twitter.com/${twitterUsername}/status/${tweetId}`; ··· 510 529 postRecord.embed = { 511 530 $type: 'app.bsky.embed.video', 512 531 video: videoBlob, 532 + thumbnail: videoThumbnailBlob, 513 533 aspectRatio: videoAspectRatio, 514 534 }; 515 535 } else if (images.length > 0) {