Skip to content

Instantly share code, notes, and snippets.

@Yuyz0112
Created March 8, 2025 03:52
Show Gist options
  • Save Yuyz0112/235c5d5003facb49c28103336fe270fb to your computer and use it in GitHub Desktop.
Save Yuyz0112/235c5d5003facb49c28103336fe270fb to your computer and use it in GitHub Desktop.
import { Easing, Series, staticFile } from "remotion";
import {
AbsoluteFill,
interpolate,
Sequence,
useCurrentFrame,
Img,
Audio,
} from "remotion";
import { z } from "zod";
import { Icon1, Icon2, Icon3, Icon4 } from "./Iconts";
import { useRef } from "react";
export const myCompSchema = z.object({
totalDurationInFrames: z.number(),
videos: z.array(
z.object({
durationInFrames: z.number(),
pic: z.string(),
title: z.string(),
bvid: z.string(),
like: z.number(),
coin: z.number(),
favorite: z.number(),
share: z.number(),
play: z.number(),
comments: z.array(
z.object({
content: z.string(),
member: z.string(),
avatar: z.string(),
})
),
})
),
});
// export const FRAME_PER_VIDEO = 360;
export const Bilibili: React.FC<z.infer<typeof myCompSchema>> = ({
videos,
totalDurationInFrames,
}) => {
const frame = useCurrentFrame();
const percent = frame / totalDurationInFrames;
const listRef = useRef<HTMLDivElement>(null);
return (
<AbsoluteFill
style={{
// backgroundColor: "#f1f3f5",
backgroundImage: `linear-gradient(90deg, #f3e7e9, #e3eeff)`,
display: "flex",
flexDirection: "row",
fontFamily: "sans-serif",
alignItems: "center",
}}
>
<Series>
<Series.Sequence durationInFrames={60 * 172}>
<Audio src={staticFile("audios/1.mp3")} />
</Series.Sequence>
<Series.Sequence durationInFrames={60 * 147}>
<Audio src={staticFile("audios/2.mp3")} />
</Series.Sequence>
<Series.Sequence durationInFrames={60 * 243}>
<Audio src={staticFile("audios/3.mp3")} />
</Series.Sequence>
<Series.Sequence durationInFrames={60 * 220}>
<Audio src={staticFile("audios/4.mp3")} />
</Series.Sequence>
</Series>
{videos.map((video, index) => {
const FRAME_PER_VIDEO = video.durationInFrames;
const frags = video.pic.split("/");
const accu = {
like: 0,
view: 0,
from: 0,
};
for (let pi = 0; pi < index; pi++) {
accu.like += videos[pi].like;
accu.view += videos[pi].play;
accu.from += videos[pi].durationInFrames;
}
const localFrame = frame - accu.from;
const localPercent = localFrame / FRAME_PER_VIDEO;
// Fade out the animation at the end
const opacity = interpolate(
localFrame,
[0, 15, FRAME_PER_VIDEO - 15, FRAME_PER_VIDEO],
[0, 1, 1, 0],
{
easing: Easing.bezier(0.1, 0.22, 0.96, 0.85),
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}
);
const ty = interpolate(
localFrame,
[0, 15, FRAME_PER_VIDEO - 15, FRAME_PER_VIDEO],
[200, 0, 0, -100],
{
easing: Easing.bezier(0.1, 0.22, 0.96, 0.85),
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}
);
const blur = interpolate(
localFrame,
[0, 15, FRAME_PER_VIDEO - 15, FRAME_PER_VIDEO],
[0, 0, 0, 10],
{
easing: Easing.bezier(0.1, 0.22, 0.96, 0.85),
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}
);
const scale = interpolate(
localFrame,
[0, 15, FRAME_PER_VIDEO - 15, FRAME_PER_VIDEO],
[0.95, 1, 1, 0.95]
);
const progress = interpolate(
localFrame,
[0, FRAME_PER_VIDEO],
[0, -100],
{
easing: Easing.bezier(1, 0.5, 0.3, 0.1),
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}
);
return (
<Sequence
key={video.bvid}
from={accu.from}
durationInFrames={FRAME_PER_VIDEO}
layout="none"
>
<div
style={{
flex: 1,
alignItems: "flex-start",
paddingLeft: 160,
justifyContent: "center",
display: "flex",
flexDirection: "column",
}}
>
<div
style={{
borderRadius: 16,
overflow: "hidden",
background: "white",
border: "1px solid rgba(0,0,0,0.19)",
width: 720,
marginBottom: 64,
padding: 16,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
}}
>
<Img
style={{
borderRadius: "50%",
height: 64,
width: 64,
marginRight: 8,
}}
src={staticFile(
`imgs/44882c2e4e40e370f328baa8583e33135eb36d07.jpg`
)}
></Img>
<div>
<div style={{ fontSize: 24 }}>Koala聊开源</div>
<div style={{ opacity: 0.5 }}>
家有程序猿初长成的退休妈妈
</div>
</div>
</div>
<div style={{ display: "flex", fontSize: 24 }}>
<div style={{ textAlign: "center", marginRight: 16 }}>
<div style={{ opacity: 0.5 }}>粉丝数</div>
<div>{Math.floor(29150 * percent).toLocaleString()}</div>
</div>
<div style={{ textAlign: "center", marginRight: 16 }}>
<div style={{ opacity: 0.5 }}>获赞数</div>
<div>
{Math.floor(
accu.like + video.like * localPercent
).toLocaleString()}
</div>
</div>
<div style={{ textAlign: "center", marginRight: 16 }}>
<div style={{ opacity: 0.5 }}>播放数</div>
<div>
{Math.floor(
accu.view + video.play * localPercent
).toLocaleString()}
</div>
</div>
</div>
</div>
<div
style={{
borderRadius: 16,
overflow: "hidden",
background: "white",
border: "1px solid rgba(0,0,0,0.19)",
width: 640,
transform: `translateY(${ty}px) scale(${scale})`,
filter: `blur(${blur}px)`,
opacity,
}}
>
<Img
src={staticFile(`imgs/${frags[frags.length - 1]}`)}
width={640}
height={400}
/>
<div
style={{
fontSize: 32,
padding: `8px 16px`,
}}
>
{video.title}
</div>
<div
style={{
padding: `8px 16px`,
color: "rgb(97, 102, 109)",
display: "flex",
alignItems: "center",
fontSize: 24,
lineHeight: `28px`,
}}
>
<Icon1 />
<span
style={{
marginRight: 24,
marginTop: 2,
}}
>
{video.like}
</span>
<Icon2 />
<span
style={{
marginRight: 24,
marginTop: 2,
}}
>
{video.coin}
</span>
<Icon3 />
<span
style={{
marginRight: 24,
marginTop: 2,
}}
>
{video.favorite}
</span>
<Icon4 />
<span
style={{
marginRight: 24,
marginTop: 2,
}}
>
{video.share}
</span>
</div>
</div>
</div>
<div
style={{
flex: 1,
alignItems: "flex-start",
justifyContent: "center",
display: "flex",
height: "80%",
overflow: "hidden",
}}
>
<div
ref={listRef}
style={{
// background: "lightpink",
transform: `translateY(calc(${progress}% + ${432}px))`,
}}
>
{video.comments.map((comment, idx) => {
const cFrag = comment.avatar.split("/");
return (
<div
key={`${video.bvid}-c-${idx}`}
style={{
borderRadius: 16,
overflow: "hidden",
background: "white",
border: "1px solid rgba(0,0,0,0.19)",
width: 640,
transform: `translateY(${0}px)`,
padding: 16,
marginBottom: 16,
display: "block",
}}
>
<div
style={{
alignItems: "center",
display: "flex",
}}
>
<Img
style={{
borderRadius: "50%",
height: 48,
width: 48,
marginRight: 8,
}}
src={staticFile(`imgs/${cFrag[cFrag.length - 1]}`)}
></Img>
<div
style={{
fontSize: 16,
}}
>
{comment.member}
</div>
</div>
<div
style={{
fontSize: 24,
marginTop: 4,
}}
>
{comment.content}
</div>
</div>
);
})}
</div>
</div>
</Sequence>
);
})}
</AbsoluteFill>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment