remote-rdr / src /common /TweetPoster.tsx
shiveshnavin's picture
Twitter working
f75bd44
import { Group, Meta, Transcript, TweetExtra } from "common-utils";
import { AbsoluteFill } from "remotion";
import { RenderUtils } from "../RenderUtils";
function formatTweetDate(date: Date) {
const hours = date.getHours();
const minutes = date.getMinutes();
const ampm = hours >= 12 ? "PM" : "AM";
const hour12 = hours % 12 === 0 ? 12 : hours % 12;
const minStr = minutes.toString().padStart(2, "0");
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const month = months[date.getMonth()];
const day = date.getDate();
const year = date.getFullYear();
return `${hour12}:${minStr} ${ampm} · ${month} ${day}, ${year}`;
}
export default function TweetPoster({
transcript,
meta,
}: {
transcript: Transcript;
meta: Meta;
}) {
const textParts = transcript.audioCaption?.words;
if (!textParts) {
return (
<AbsoluteFill color="#ffffff">
<h1>
<div>Empty tweet text!</div>
</h1>
</AbsoluteFill>
);
}
const { width, height } = meta.resolution || { width: 1080, height: 1920 };
// Scale solely from WIDTH (as requested)
const W = Math.max(320, width); // guard for tiny canvases
// Outer padding around the card (kept modest so card fills width but not flush)
const outerPad = Math.max(Math.round(W * 0.04), 16); // 4% of width, min 16px
// Card internal padding
const boxPad = Math.max(Math.round(W * 0.03), 14); // 3% of width, min 14px
// Avatar + typography strictly from width
const avatarSize = Math.max(Math.round(W * 0.08), 56); // 8% of width
const fontNamePx = Math.round(W * 0.035); // display name
const fontUserPx = Math.round(W * 0.028); // @username
const fontMainPx = Math.round(W * 0.042); // tweet text
const fontDatePx = Math.round(W * 0.026); // footer date
// Reasonable clamps to avoid extremes on very large/small canvases
const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v));
const fontName = `${clamp(fontNamePx, 16, 40)}px`;
const fontUsername = `${clamp(fontUserPx, 14, 32)}px`;
const fontMain = `${clamp(fontMainPx, 18, 48)}px`;
const fontDate = `${clamp(fontDatePx, 12, 28)}px`;
let bgColor = (transcript.extras as TweetExtra)?.textBgColor || "#ffffff";
const fullName = textParts[0]?.word;
const fullNameStyle = textParts[0]?.textStyle;
const username = textParts[1]?.word;
const usernameStyle = textParts[1]?.textStyle;
const tweetLine1 = textParts[2]?.word;
const tweetLine1Style = textParts[2]?.textStyle;
const tweetLine2 = textParts[3]?.word;
const tweetLine2Style = textParts[3]?.textStyle;
const avatar = RenderUtils.tryStaticFile(transcript.mediaAbsPaths?.[0]?.path);
return (
<AbsoluteFill
style={{
background: bgColor,
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
boxSizing: "border-box",
padding: 0,
}}
>
{/* Outer pad keeps some breathing room; box fills remaining width */}
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
boxSizing: "border-box",
padding: outerPad,
}}
>
{/* Tweet box now fully fits parent width with the outerPad margin */}
<div
style={{
width: "100%",
// height is auto so small/large content won't crop;
// allow it to grow and just keep nice rounding/shadow
background: "#fff",
border: "1px solid #e1e8ed",
borderRadius: Math.max(Math.round(W * 0.02), 16),
boxShadow: `0 ${Math.round(W * 0.04)}px ${Math.round(
W * 0.08
)}px rgba(0,0,0,0.10)`,
padding: `${boxPad}px ${Math.round(boxPad * 1.2)}px`,
fontFamily: "Segoe UI, Arial, sans-serif",
boxSizing: "border-box",
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
// Prevent cropping: let content define height; wrap long words
overflow: "visible",
wordBreak: "break-word",
}}
>
{/* Header */}
<div
style={{
display: "flex",
alignItems: "center",
marginBottom: boxPad,
minWidth: 0,
}}
>
<img
src={avatar}
alt="avatar"
style={{
width: avatarSize,
height: avatarSize,
minWidth: 40,
minHeight: 40,
borderRadius: "50%",
marginRight: boxPad,
border: "2px solid #e1e8ed",
objectFit: "cover",
flexShrink: 0,
}}
/>
<div style={{ minWidth: 0 }}>
<div
style={{
fontWeight: 700,
fontSize: fontName,
lineHeight: 1.15,
whiteSpace: "pre-wrap",
...fullNameStyle,
}}
>
{fullName}
</div>
<div
style={{
color: "#657786",
fontSize: fontUsername,
lineHeight: 1.15,
whiteSpace: "pre-wrap",
...usernameStyle,
}}
>
{username}
</div>
</div>
</div>
{/* Tweet text */}
<div
style={{
fontSize: fontMain,
lineHeight: 1.45,
marginBottom: boxPad,
whiteSpace: "pre-wrap",
}}
>
<span style={tweetLine1Style}>{tweetLine1}</span>
{tweetLine2 && (
<>
{"\n"}
<span style={tweetLine2Style}>{tweetLine2}</span>
</>
)}
</div>
{/* Footer */}
<div
style={{
color: "#657786",
fontSize: fontDate,
marginTop: Math.round(boxPad * 0.5),
display: "flex",
gap: Math.round(W * 0.01),
alignItems: "baseline",
flexWrap: "wrap",
}}
>
<span>{formatTweetDate(new Date())}</span>
<span>·</span>
<span style={{ color: "#1da1f2" }}>Follow</span>
</div>
</div>
</div>
</AbsoluteFill>
);
}