Created
March 8, 2025 03:52
-
-
Save Yuyz0112/235c5d5003facb49c28103336fe270fb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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