shiveshnavin commited on
Commit
f75bd44
1 Parent(s): 7ec5753

Twitter working

Browse files
src/Compositions.tsx CHANGED
@@ -14,7 +14,8 @@ import { YoutubeVideoScene } from './youtube/YoutubeVideoScene';
14
  import { LinkedinFullSysDesignComposition } from './linkedin-video/LinkedinFullSysDesignComposition';
15
  import { PaperDriveComposition } from './paperdrive/PaperDriveComposition';
16
  import { PaperDriveHorizontalCoverComposition } from './paperdrive/PaperDriveHorizontalCoverComposition';
17
- import { PosterSingleTextWithBGExtra, Transcript } from 'common-utils';
 
18
 
19
 
20
 
@@ -257,6 +258,19 @@ export const Compositions: React.FC = () => {
257
  }]
258
  } as Transcript}
259
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  </>
261
  );
262
  };
 
14
  import { LinkedinFullSysDesignComposition } from './linkedin-video/LinkedinFullSysDesignComposition';
15
  import { PaperDriveComposition } from './paperdrive/PaperDriveComposition';
16
  import { PaperDriveHorizontalCoverComposition } from './paperdrive/PaperDriveHorizontalCoverComposition';
17
+ import { PosterSingleTextWithBGExtra, Transcript, TweetExtra } from 'common-utils';
18
+ import TweetPoster from './common/TweetPoster';
19
 
20
 
21
 
 
258
  }]
259
  } as Transcript}
260
  />
261
+
262
+ <Still
263
+ id="TweetPoster"
264
+ component={TweetPoster}
265
+ width={Script.meta.resolution?.width || 1920}
266
+ height={Script.meta.resolution?.height || 1080}
267
+ defaultProps={
268
+ {
269
+ transcript: Script.transcript.find(t => t.extras?.template == "tweet"),
270
+ meta: Script.meta
271
+ }
272
+ }
273
+ />
274
  </>
275
  );
276
  };
src/RenderUtils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { random, useCurrentFrame } from "remotion";
2
 
3
 
4
  function levenshteinDistance(str1: string, str2: string) {
@@ -19,6 +19,17 @@ function levenshteinDistance(str1: string, str2: string) {
19
  }
20
  export class RenderUtils {
21
 
 
 
 
 
 
 
 
 
 
 
 
22
  public static convertVHToPixels(vh: string) {
23
  return Math.round(window.innerHeight * (parseInt(vh.replace("vh", "")) / 100));
24
  }
@@ -122,22 +133,17 @@ export class RenderUtils {
122
 
123
  public static getFileName(filePath: string, subdirpath?: string): string | undefined {
124
  if (!filePath)
125
- return undefined
126
  if (filePath.startsWith("http"))
127
- return filePath
128
- const slashCount = (filePath.replace("./", "").split("/").length - 1);
129
- if (slashCount > 0) {
130
- let pub = filePath.replace("./", "")
131
- return pub
132
- }
133
 
 
134
  const match = filePath.match(/([^\/\\]+)$/);
135
- let folmat = match ? match[1] : '';
136
  if (subdirpath) {
137
- return subdirpath + "/" + folmat
138
- }
139
- else {
140
- return folmat
141
  }
142
  }
143
 
 
1
+ import { random, staticFile, useCurrentFrame } from "remotion";
2
 
3
 
4
  function levenshteinDistance(str1: string, str2: string) {
 
19
  }
20
  export class RenderUtils {
21
 
22
+ public static tryStaticFile(file: string) {
23
+ console.log('file', file)
24
+ try {
25
+ return staticFile(file);
26
+ } catch (e) {
27
+ let fileName = RenderUtils.getFileName(file)
28
+ console.log('fileName', file)
29
+ return staticFile(fileName!!);
30
+ }
31
+ }
32
+
33
  public static convertVHToPixels(vh: string) {
34
  return Math.round(window.innerHeight * (parseInt(vh.replace("vh", "")) / 100));
35
  }
 
133
 
134
  public static getFileName(filePath: string, subdirpath?: string): string | undefined {
135
  if (!filePath)
136
+ return undefined;
137
  if (filePath.startsWith("http"))
138
+ return filePath;
 
 
 
 
 
139
 
140
+ // Always extract just the filename from any path
141
  const match = filePath.match(/([^\/\\]+)$/);
142
+ let filename = match ? match[1] : '';
143
  if (subdirpath) {
144
+ return subdirpath + "/" + filename;
145
+ } else {
146
+ return filename;
 
147
  }
148
  }
149
 
src/common/TweetPoster.tsx ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Group, Meta, Transcript, TweetExtra } from "common-utils";
2
+ import { AbsoluteFill } from "remotion";
3
+ import { RenderUtils } from "../RenderUtils";
4
+
5
+ function formatTweetDate(date: Date) {
6
+ const hours = date.getHours();
7
+ const minutes = date.getMinutes();
8
+ const ampm = hours >= 12 ? "PM" : "AM";
9
+ const hour12 = hours % 12 === 0 ? 12 : hours % 12;
10
+ const minStr = minutes.toString().padStart(2, "0");
11
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
12
+ const month = months[date.getMonth()];
13
+ const day = date.getDate();
14
+ const year = date.getFullYear();
15
+ return `${hour12}:${minStr} ${ampm} 路 ${month} ${day}, ${year}`;
16
+ }
17
+
18
+ export default function TweetPoster({
19
+ transcript,
20
+ meta,
21
+ }: {
22
+ transcript: Transcript;
23
+ meta: Meta;
24
+ }) {
25
+ const textParts = transcript.audioCaption?.words;
26
+ if (!textParts) {
27
+ return (
28
+ <AbsoluteFill color="#ffffff">
29
+ <h1>
30
+ <div>Empty tweet text!</div>
31
+ </h1>
32
+ </AbsoluteFill>
33
+ );
34
+ }
35
+
36
+ const { width, height } = meta.resolution || { width: 1080, height: 1920 };
37
+
38
+ // Scale solely from WIDTH (as requested)
39
+ const W = Math.max(320, width); // guard for tiny canvases
40
+
41
+ // Outer padding around the card (kept modest so card fills width but not flush)
42
+ const outerPad = Math.max(Math.round(W * 0.04), 16); // 4% of width, min 16px
43
+
44
+ // Card internal padding
45
+ const boxPad = Math.max(Math.round(W * 0.03), 14); // 3% of width, min 14px
46
+
47
+ // Avatar + typography strictly from width
48
+ const avatarSize = Math.max(Math.round(W * 0.08), 56); // 8% of width
49
+ const fontNamePx = Math.round(W * 0.035); // display name
50
+ const fontUserPx = Math.round(W * 0.028); // @username
51
+ const fontMainPx = Math.round(W * 0.042); // tweet text
52
+ const fontDatePx = Math.round(W * 0.026); // footer date
53
+
54
+ // Reasonable clamps to avoid extremes on very large/small canvases
55
+ const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v));
56
+ const fontName = `${clamp(fontNamePx, 16, 40)}px`;
57
+ const fontUsername = `${clamp(fontUserPx, 14, 32)}px`;
58
+ const fontMain = `${clamp(fontMainPx, 18, 48)}px`;
59
+ const fontDate = `${clamp(fontDatePx, 12, 28)}px`;
60
+
61
+ let bgColor = (transcript.extras as TweetExtra)?.textBgColor || "#ffffff";
62
+ const fullName = textParts[0]?.word;
63
+ const fullNameStyle = textParts[0]?.textStyle;
64
+ const username = textParts[1]?.word;
65
+ const usernameStyle = textParts[1]?.textStyle;
66
+ const tweetLine1 = textParts[2]?.word;
67
+ const tweetLine1Style = textParts[2]?.textStyle;
68
+ const tweetLine2 = textParts[3]?.word;
69
+ const tweetLine2Style = textParts[3]?.textStyle;
70
+ const avatar = RenderUtils.tryStaticFile(transcript.mediaAbsPaths?.[0]?.path);
71
+
72
+ return (
73
+ <AbsoluteFill
74
+ style={{
75
+ background: bgColor,
76
+ display: "flex",
77
+ alignItems: "center",
78
+ justifyContent: "center",
79
+ width: "100%",
80
+ height: "100%",
81
+ boxSizing: "border-box",
82
+ padding: 0,
83
+ }}
84
+ >
85
+ {/* Outer pad keeps some breathing room; box fills remaining width */}
86
+ <div
87
+ style={{
88
+ width: "100%",
89
+ height: "100%",
90
+ display: "flex",
91
+ alignItems: "center",
92
+ justifyContent: "center",
93
+ boxSizing: "border-box",
94
+ padding: outerPad,
95
+ }}
96
+ >
97
+ {/* Tweet box now fully fits parent width with the outerPad margin */}
98
+ <div
99
+ style={{
100
+ width: "100%",
101
+ // height is auto so small/large content won't crop;
102
+ // allow it to grow and just keep nice rounding/shadow
103
+ background: "#fff",
104
+ border: "1px solid #e1e8ed",
105
+ borderRadius: Math.max(Math.round(W * 0.02), 16),
106
+ boxShadow: `0 ${Math.round(W * 0.04)}px ${Math.round(
107
+ W * 0.08
108
+ )}px rgba(0,0,0,0.10)`,
109
+ padding: `${boxPad}px ${Math.round(boxPad * 1.2)}px`,
110
+ fontFamily: "Segoe UI, Arial, sans-serif",
111
+ boxSizing: "border-box",
112
+ display: "flex",
113
+ flexDirection: "column",
114
+ justifyContent: "flex-start",
115
+
116
+ // Prevent cropping: let content define height; wrap long words
117
+ overflow: "visible",
118
+ wordBreak: "break-word",
119
+ }}
120
+ >
121
+ {/* Header */}
122
+ <div
123
+ style={{
124
+ display: "flex",
125
+ alignItems: "center",
126
+ marginBottom: boxPad,
127
+ minWidth: 0,
128
+ }}
129
+ >
130
+ <img
131
+ src={avatar}
132
+ alt="avatar"
133
+ style={{
134
+ width: avatarSize,
135
+ height: avatarSize,
136
+ minWidth: 40,
137
+ minHeight: 40,
138
+ borderRadius: "50%",
139
+ marginRight: boxPad,
140
+ border: "2px solid #e1e8ed",
141
+ objectFit: "cover",
142
+ flexShrink: 0,
143
+ }}
144
+ />
145
+ <div style={{ minWidth: 0 }}>
146
+ <div
147
+ style={{
148
+ fontWeight: 700,
149
+ fontSize: fontName,
150
+ lineHeight: 1.15,
151
+ whiteSpace: "pre-wrap",
152
+ ...fullNameStyle,
153
+ }}
154
+ >
155
+ {fullName}
156
+ </div>
157
+ <div
158
+ style={{
159
+ color: "#657786",
160
+ fontSize: fontUsername,
161
+ lineHeight: 1.15,
162
+ whiteSpace: "pre-wrap",
163
+ ...usernameStyle,
164
+ }}
165
+ >
166
+ {username}
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ {/* Tweet text */}
172
+ <div
173
+ style={{
174
+ fontSize: fontMain,
175
+ lineHeight: 1.45,
176
+ marginBottom: boxPad,
177
+ whiteSpace: "pre-wrap",
178
+ }}
179
+ >
180
+ <span style={tweetLine1Style}>{tweetLine1}</span>
181
+ {tweetLine2 && (
182
+ <>
183
+ {"\n"}
184
+ <span style={tweetLine2Style}>{tweetLine2}</span>
185
+ </>
186
+ )}
187
+ </div>
188
+
189
+ {/* Footer */}
190
+ <div
191
+ style={{
192
+ color: "#657786",
193
+ fontSize: fontDate,
194
+ marginTop: Math.round(boxPad * 0.5),
195
+ display: "flex",
196
+ gap: Math.round(W * 0.01),
197
+ alignItems: "baseline",
198
+ flexWrap: "wrap",
199
+ }}
200
+ >
201
+ <span>{formatTweetDate(new Date())}</span>
202
+ <span>路</span>
203
+ <span style={{ color: "#1da1f2" }}>Follow</span>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </AbsoluteFill>
208
+ );
209
+ }