@revideo/create 0.3.5-yellow.968 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/README.md +39 -0
- package/examples/avatar-with-background/package-lock.json +3162 -0
- package/examples/avatar-with-background/package.json +26 -0
- package/examples/avatar-with-background/src/project.meta +31 -0
- package/examples/avatar-with-background/src/project.ts +6 -0
- package/examples/avatar-with-background/src/render.ts +36 -0
- package/examples/avatar-with-background/src/revideo.d.ts +1 -0
- package/examples/avatar-with-background/src/scenes/example.tsx +28 -0
- package/examples/avatar-with-background/tsconfig.json +9 -0
- package/examples/avatar-with-background/vite.config.ts +8 -0
- package/examples/default/package.json +24 -0
- package/examples/default/src/project.meta +31 -0
- package/examples/default/src/project.ts +7 -0
- package/examples/default/src/render.ts +17 -0
- package/examples/default/src/revideo.d.ts +1 -0
- package/examples/default/src/scenes/example.tsx +38 -0
- package/examples/default/tsconfig.json +9 -0
- package/examples/google-cloud-run/Dockerfile +56 -0
- package/examples/google-cloud-run/README.md +35 -0
- package/examples/google-cloud-run/package-lock.json +3862 -0
- package/examples/google-cloud-run/package.json +28 -0
- package/examples/google-cloud-run/src/index.ts +39 -0
- package/examples/google-cloud-run/src/project.meta +31 -0
- package/examples/google-cloud-run/src/project.ts +7 -0
- package/examples/google-cloud-run/src/revideo.d.ts +1 -0
- package/examples/google-cloud-run/src/scenes/example.meta +5 -0
- package/examples/google-cloud-run/src/scenes/example.tsx +30 -0
- package/examples/google-cloud-run/tsconfig.json +9 -0
- package/examples/google-cloud-run/vite.config.ts +9 -0
- package/examples/google-cloud-run-parallelized/README.md +127 -0
- package/examples/google-cloud-run-parallelized/render-orchestrator/Dockerfile +56 -0
- package/examples/google-cloud-run-parallelized/render-orchestrator/package-lock.json +2588 -0
- package/examples/google-cloud-run-parallelized/render-orchestrator/package.json +22 -0
- package/examples/google-cloud-run-parallelized/render-orchestrator/src/index.ts +72 -0
- package/examples/google-cloud-run-parallelized/render-orchestrator/src/revideo.d.ts +1 -0
- package/examples/google-cloud-run-parallelized/render-orchestrator/tsconfig.json +10 -0
- package/examples/google-cloud-run-parallelized/render-worker/.gcloudignore +17 -0
- package/examples/google-cloud-run-parallelized/render-worker/.puppeteerrc.cjs +9 -0
- package/examples/google-cloud-run-parallelized/render-worker/package-lock.json +4871 -0
- package/examples/google-cloud-run-parallelized/render-worker/package.json +30 -0
- package/examples/google-cloud-run-parallelized/render-worker/src/index.ts +39 -0
- package/examples/google-cloud-run-parallelized/render-worker/src/project.ts +6 -0
- package/examples/google-cloud-run-parallelized/render-worker/src/revideo.d.ts +1 -0
- package/examples/google-cloud-run-parallelized/render-worker/src/scenes/example.tsx +30 -0
- package/examples/google-cloud-run-parallelized/render-worker/tsconfig.json +13 -0
- package/examples/google-cloud-run-parallelized/render-worker/vite.config.ts +9 -0
- package/examples/logo.svg +6 -0
- package/examples/logo_dark.svg +6 -0
- package/examples/marketing-templates/README.md +10 -0
- package/examples/marketing-templates/marketing-template/package-lock.json +3824 -0
- package/examples/marketing-templates/marketing-template/package.json +24 -0
- package/examples/marketing-templates/marketing-template/src/globals.css +0 -0
- package/examples/marketing-templates/marketing-template/src/project.meta +31 -0
- package/examples/marketing-templates/marketing-template/src/project.ts +9 -0
- package/examples/marketing-templates/marketing-template/src/render.ts +17 -0
- package/examples/marketing-templates/marketing-template/src/revideo.d.ts +1 -0
- package/examples/marketing-templates/marketing-template/src/scenes/example.meta +5 -0
- package/examples/marketing-templates/marketing-template/src/scenes/example.tsx +67 -0
- package/examples/marketing-templates/marketing-template/tsconfig.json +9 -0
- package/examples/marketing-templates/marketing-template/vite.config.ts +9 -0
- package/examples/marketing-templates/multiple-videos-in-one/package-lock.json +3824 -0
- package/examples/marketing-templates/multiple-videos-in-one/package.json +24 -0
- package/examples/marketing-templates/multiple-videos-in-one/src/project.meta +31 -0
- package/examples/marketing-templates/multiple-videos-in-one/src/project.ts +9 -0
- package/examples/marketing-templates/multiple-videos-in-one/src/render.ts +17 -0
- package/examples/marketing-templates/multiple-videos-in-one/src/revideo.d.ts +1 -0
- package/examples/marketing-templates/multiple-videos-in-one/src/scenes/example.meta +5 -0
- package/examples/marketing-templates/multiple-videos-in-one/src/scenes/example.tsx +122 -0
- package/examples/marketing-templates/multiple-videos-in-one/tsconfig.json +9 -0
- package/examples/marketing-templates/multiple-videos-in-one/vite.config.ts +9 -0
- package/examples/saas-template/.prettierrc +9 -0
- package/examples/saas-template/README.md +28 -0
- package/examples/saas-template/next/app/actions.tsx +113 -0
- package/examples/saas-template/next/app/api/render/route.ts +22 -0
- package/examples/saas-template/next/app/globals.css +3 -0
- package/examples/saas-template/next/app/layout.tsx +25 -0
- package/examples/saas-template/next/app/page.tsx +182 -0
- package/examples/saas-template/next/next.config.mjs +4 -0
- package/examples/saas-template/next/package.json +34 -0
- package/examples/saas-template/next/postcss.config.js +6 -0
- package/examples/saas-template/next/public/favicon.ico +0 -0
- package/examples/saas-template/next/public/next.svg +1 -0
- package/examples/saas-template/next/public/vercel.svg +1 -0
- package/examples/saas-template/next/tailwind.config.ts +30 -0
- package/examples/saas-template/next/tsconfig.json +26 -0
- package/examples/saas-template/next/utils/parse.ts +35 -0
- package/examples/saas-template/revideo/package.json +24 -0
- package/examples/saas-template/revideo/src/global.css +1 -0
- package/examples/saas-template/revideo/src/project.meta +31 -0
- package/examples/saas-template/revideo/src/project.ts +9 -0
- package/examples/saas-template/revideo/src/render.ts +14 -0
- package/examples/saas-template/revideo/src/revideo.d.ts +1 -0
- package/examples/saas-template/revideo/src/scenes/example.meta +5 -0
- package/examples/saas-template/revideo/src/scenes/example.tsx +178 -0
- package/examples/saas-template/revideo/tsconfig.json +9 -0
- package/examples/saas-template/revideo/vite.config.ts +6 -0
- package/examples/stitching-videos/README.md +7 -0
- package/examples/stitching-videos/package-lock.json +2801 -0
- package/examples/stitching-videos/package.json +23 -0
- package/examples/stitching-videos/src/project.meta +31 -0
- package/examples/stitching-videos/src/project.ts +26 -0
- package/examples/stitching-videos/src/render.ts +58 -0
- package/examples/stitching-videos/src/revideo.d.ts +1 -0
- package/examples/stitching-videos/src/scenes/example.tsx +30 -0
- package/examples/stitching-videos/tsconfig.json +9 -0
- package/examples/stitching-videos/vite.config.ts +8 -0
- package/examples/youtube-shorts/README.md +65 -0
- package/examples/youtube-shorts/package-lock.json +3914 -0
- package/examples/youtube-shorts/package.json +30 -0
- package/examples/youtube-shorts/src/global.css +3 -0
- package/examples/youtube-shorts/src/metadata.json +635 -0
- package/examples/youtube-shorts/src/project.meta +31 -0
- package/examples/youtube-shorts/src/project.ts +9 -0
- package/examples/youtube-shorts/src/render.ts +40 -0
- package/examples/youtube-shorts/src/revideo.d.ts +1 -0
- package/examples/youtube-shorts/src/scenes/example.tsx +211 -0
- package/examples/youtube-shorts/src/utils.ts +123 -0
- package/examples/youtube-shorts/tsconfig.json +9 -0
- package/examples/youtube-shorts/vite.config.ts +8 -0
- package/index.js +33 -11
- package/package.json +9 -8
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "veed-template",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "vite",
|
|
7
|
+
"serve": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"render": "tsc && node dist/render.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@revideo/core": "0.3.4",
|
|
13
|
+
"@revideo/2d": "0.3.4",
|
|
14
|
+
"@revideo/renderer": "0.3.4",
|
|
15
|
+
"@revideo/vite-plugin": "0.3.4",
|
|
16
|
+
"@revideo/ffmpeg": "0.3.4"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@revideo/ui": "0.3.4",
|
|
20
|
+
"@revideo/cli": "^0.3.4",
|
|
21
|
+
"typescript": "^5.2.2",
|
|
22
|
+
"vite": "^4.5"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 0,
|
|
3
|
+
"shared": {
|
|
4
|
+
"background": "rgb(255,255,255)",
|
|
5
|
+
"range": [
|
|
6
|
+
0,
|
|
7
|
+
null
|
|
8
|
+
],
|
|
9
|
+
"size": {
|
|
10
|
+
"x": 1920,
|
|
11
|
+
"y": 1080
|
|
12
|
+
},
|
|
13
|
+
"audioOffset": 0
|
|
14
|
+
},
|
|
15
|
+
"preview": {
|
|
16
|
+
"fps": 30,
|
|
17
|
+
"resolutionScale": 1
|
|
18
|
+
},
|
|
19
|
+
"rendering": {
|
|
20
|
+
"fps": 30,
|
|
21
|
+
"resolutionScale": 1,
|
|
22
|
+
"colorSpace": "srgb",
|
|
23
|
+
"exporter": {
|
|
24
|
+
"name": "@revideo/core/ffmpeg",
|
|
25
|
+
"options": {
|
|
26
|
+
"fastStart": true,
|
|
27
|
+
"includeAudio": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {makeProject} from '@revideo/core';
|
|
2
|
+
|
|
3
|
+
import example from './scenes/example?scene';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export default makeProject({
|
|
7
|
+
scenes: [example],
|
|
8
|
+
variables: { backgroundColor: "#FDCFE5", texts: ["gift cards", "discounts", "+ more!!"]} // ["gift cards", "discounts", "+ more!!"] ["events", "special offers", "and more..."]
|
|
9
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {renderVideo} from '@revideo/renderer';
|
|
2
|
+
|
|
3
|
+
async function render() {
|
|
4
|
+
console.log('Rendering video...');
|
|
5
|
+
|
|
6
|
+
// This is the main function that renders the video
|
|
7
|
+
const file = await renderVideo(
|
|
8
|
+
'./vite.config.ts',
|
|
9
|
+
{fill: 'orange'},
|
|
10
|
+
() => {},
|
|
11
|
+
{logProgress: true},
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
console.log(`Rendered video to ${file}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="@revideo/core/project" />
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {Grid, Img, Rect, Txt, View2D, makeScene2D} from '@revideo/2d';
|
|
2
|
+
import {all, chain, useScene, createRef, waitFor, Reference} from '@revideo/core';
|
|
3
|
+
|
|
4
|
+
export default makeScene2D(function* (view) {
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const backgroundRef = createRef<Rect>();
|
|
8
|
+
|
|
9
|
+
yield view.add(
|
|
10
|
+
<>
|
|
11
|
+
<Rect
|
|
12
|
+
fill={"#9accf2"}
|
|
13
|
+
position={[useScene().getSize().x*0.25-1920*0.5, useScene().getSize().y*0.25-1080*0.5]}
|
|
14
|
+
size={['40%', '40%']}
|
|
15
|
+
ref={backgroundRef}
|
|
16
|
+
/>
|
|
17
|
+
</>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
yield* video(view, backgroundRef, "#9accf2", ["gift cards", "discounts", "+ more!!"]);
|
|
21
|
+
|
|
22
|
+
const topRight = createRef<Rect>();
|
|
23
|
+
const bottomLeft = createRef<Rect>();
|
|
24
|
+
const bottomRight = createRef<Rect>();
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
view.add(
|
|
28
|
+
<>
|
|
29
|
+
<Rect
|
|
30
|
+
fill={"#FDCFE5"}
|
|
31
|
+
position={[useScene().getSize().x*0.25-1920*0.5, useScene().getSize().y*0.25-1080*0.5]}
|
|
32
|
+
size={['40%', '40%']}
|
|
33
|
+
ref={topRight}
|
|
34
|
+
zIndex={-1}
|
|
35
|
+
/>
|
|
36
|
+
<Rect
|
|
37
|
+
fill={"#FDB827"}
|
|
38
|
+
position={[useScene().getSize().x*0.25-1920*0.5, useScene().getSize().y*0.25-1080*0.5]}
|
|
39
|
+
size={['40%', '40%']}
|
|
40
|
+
ref={bottomLeft}
|
|
41
|
+
zIndex={-1}
|
|
42
|
+
/>
|
|
43
|
+
<Rect
|
|
44
|
+
fill={"#8BF8A7"}
|
|
45
|
+
position={[useScene().getSize().x*0.25-1920*0.5, useScene().getSize().y*0.25-1080*0.5]}
|
|
46
|
+
size={['40%', '40%']}
|
|
47
|
+
ref={bottomRight}
|
|
48
|
+
zIndex={-1}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
yield* all(
|
|
55
|
+
bottomLeft().y(useScene().getSize().y*0.75-1080*0.5, 1),
|
|
56
|
+
topRight().x(useScene().getSize().x*0.75-1920*0.5, 1),
|
|
57
|
+
bottomRight().position([useScene().getSize().x*0.75-1920*0.5, useScene().getSize().y*0.75-1080*0.5], 1)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
yield* all(
|
|
61
|
+
video(view, bottomLeft, "#FDB827", ["gift cards", "discounts", "+ more!!"]),
|
|
62
|
+
video(view, topRight, "#FDCFE5", ["gift cards", "discounts", "+ more!!"]),
|
|
63
|
+
video(view, bottomRight, "#8BF8A7", ["gift cards", "discounts", "+ more!!"]),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
function* video(view: View2D, backgroundRef: Reference<Rect>, backgroundColor: string, texts: string[]){
|
|
69
|
+
const grid = createRef<Grid>();
|
|
70
|
+
const headingRef = createRef<Txt>()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
backgroundRef().add(
|
|
74
|
+
<Grid
|
|
75
|
+
ref={grid}
|
|
76
|
+
width={backgroundRef().width()}
|
|
77
|
+
height={backgroundRef().height()}
|
|
78
|
+
stroke={'#999'}
|
|
79
|
+
end={0}
|
|
80
|
+
spacing={150*0.4}
|
|
81
|
+
/>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
yield* all(
|
|
85
|
+
grid().end(0.5, 0.5).to(1, 0.5).wait(0.1),
|
|
86
|
+
grid().start(0.5, 0.5).to(0, 0.5).wait(0.1),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
backgroundRef().add(<Txt fontSize={1*0.40} fontWeight={600} ref={headingRef} position={[-backgroundRef().width()*0.3645, -backgroundRef().width()*0.05208]} opacity={0.2} textWrap={true} width={"1%"} textAlign={"left"} fontFamily={"Sans-Serif"}>BLACK FRIDAY</Txt>)
|
|
90
|
+
yield* all(headingRef().fontSize(180*0.40, 0.5), headingRef().opacity(1, 0.5));
|
|
91
|
+
|
|
92
|
+
const txtRef = createRef<Txt>();
|
|
93
|
+
const subtitle = <Txt fontSize={60*0.40} ref={txtRef} fill={backgroundColor} zIndex={1} fontFamily={"Sans-Serif"}>Up to 95% off</Txt>
|
|
94
|
+
|
|
95
|
+
const textBoxRef = createRef<Txt>();
|
|
96
|
+
|
|
97
|
+
backgroundRef().add(<Rect left={[headingRef().left().x, 180*0.40]} ref={textBoxRef} width={txtRef().width()+60*0.40} height={40} zIndex={0} fill={"black"}/>);
|
|
98
|
+
backgroundRef().add(<Txt position={textBoxRef().position} width={textBoxRef().width()} paddingLeft={5} textAlign={"left"} fontSize={60*0.40} ref={txtRef} fill={backgroundColor} zIndex={1} fontFamily={"Sans-Serif"}></Txt>)
|
|
99
|
+
|
|
100
|
+
yield* txtRef().text("Up to 95% off", 1)
|
|
101
|
+
yield* addItems(backgroundRef, backgroundColor, texts);
|
|
102
|
+
|
|
103
|
+
yield* waitFor(1);
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function* addItems(background: Reference<Rect>, backgroundColor: string, texts: string[]){
|
|
108
|
+
let yPos = 0;
|
|
109
|
+
for(let i=0; i< texts.length; i++){
|
|
110
|
+
const txtRef1 = createRef<Txt>();
|
|
111
|
+
const subtitle1 = <Txt fontSize={90*0.40} ref={txtRef1} fill={backgroundColor} zIndex={1} fontFamily={"Sans-Serif"}>{texts[i]}</Txt>
|
|
112
|
+
|
|
113
|
+
const textBoxRef1 = createRef<Txt>();
|
|
114
|
+
|
|
115
|
+
background().add(<Rect left={[50, yPos]} height={30*0.40} lineWidth={2} radius={30*0.40} stroke={"black"} ref={textBoxRef1} width={txtRef1().width()+150*0.40} zIndex={0} padding={70*0.40} fill={"white"}/>);
|
|
116
|
+
background().add(<Txt position={textBoxRef1().position} width={textBoxRef1().width()} textAlign={"center"} fontSize={90*0.40} fill={"black"} zIndex={1} fontFamily={"Sans-Serif"}>{texts[i]}</Txt>)
|
|
117
|
+
yPos = textBoxRef1().y() + textBoxRef1().height()-2.5;
|
|
118
|
+
yield* waitFor(0.5);
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Revideo Next.js Template
|
|
2
|
+
|
|
3
|
+
This is the [Revideo](https://github.com/redotvideo/revideo) NextJS starter
|
|
4
|
+
project bootstrapped with `npm init @revideo@latest`.
|
|
5
|
+
|
|
6
|
+
## Getting Started
|
|
7
|
+
|
|
8
|
+
First, install the dependencies both in the next/ and the revideo/ directories:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
(cd next && npm install) &&
|
|
12
|
+
(cd revideo && npm install)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then, serve the Revideo project using the CLI:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
(cd revideo && npx revideo serve)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Now you can start your NextJS app using the following command:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
(cd next && npm run dev)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the
|
|
28
|
+
result. Happy developing!
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import pLimit from 'p-limit';
|
|
4
|
+
|
|
5
|
+
const PER_PAGE = 30;
|
|
6
|
+
|
|
7
|
+
interface ReturnType {
|
|
8
|
+
stargazerTimes: number[];
|
|
9
|
+
status: 'success' | 'rate-limit' | 'error';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function getStargazerTimesForPage(
|
|
13
|
+
repo: `${string}/${string}`,
|
|
14
|
+
page: number,
|
|
15
|
+
headers: HeadersInit,
|
|
16
|
+
): Promise<ReturnType> {
|
|
17
|
+
const response = await fetch(
|
|
18
|
+
`https://api.github.com/repos/${repo}/stargazers?page=${page}&per_page=${PER_PAGE}`,
|
|
19
|
+
{
|
|
20
|
+
headers: {
|
|
21
|
+
Accept: 'application/vnd.github.v3.star+json',
|
|
22
|
+
...headers,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (response.status === 403) {
|
|
28
|
+
return {stargazerTimes: [], status: 'rate-limit'};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
return {stargazerTimes: [], status: 'error'};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
const dates: Date[] = data.map((stargazer: any) => new Date(stargazer.starred_at));
|
|
37
|
+
const msTimes = dates.map((date) => date.getTime());
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
stargazerTimes: msTimes,
|
|
41
|
+
status: 'success',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const limit = pLimit(10);
|
|
46
|
+
|
|
47
|
+
async function getTimes(
|
|
48
|
+
repo: `${string}/${string}`,
|
|
49
|
+
totalPages: number,
|
|
50
|
+
headers: HeadersInit,
|
|
51
|
+
): Promise<ReturnType> {
|
|
52
|
+
const stargazerPromises: Promise<ReturnType>[] = [];
|
|
53
|
+
|
|
54
|
+
// Fetch all pages in parallel
|
|
55
|
+
for (let page = 1; page <= totalPages; page++) {
|
|
56
|
+
stargazerPromises.push(limit(() => getStargazerTimesForPage(repo, page, headers)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stargazerTimes = await Promise.all(stargazerPromises);
|
|
60
|
+
|
|
61
|
+
// In case of failure, return the first error
|
|
62
|
+
const objectsThatFailed = stargazerTimes.filter(({status}) => status !== 'success');
|
|
63
|
+
if (stargazerTimes.some(({status}) => status !== 'success')) {
|
|
64
|
+
return {
|
|
65
|
+
stargazerTimes: [],
|
|
66
|
+
status: objectsThatFailed[0].status,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stargazerMs = stargazerTimes.map(({stargazerTimes}) => stargazerTimes).flat();
|
|
71
|
+
const sortedMs = stargazerMs.sort((a, b) => a - b);
|
|
72
|
+
|
|
73
|
+
const firstMs = sortedMs[0];
|
|
74
|
+
const relativeMs = sortedMs.map((ms) => ms - firstMs);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
stargazerTimes: relativeMs,
|
|
78
|
+
status: 'success',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function getGithubRepositoryInfo(
|
|
83
|
+
repoName: `${string}/${string}`,
|
|
84
|
+
key?: string,
|
|
85
|
+
): Promise<ReturnType & {repoImage: string}> {
|
|
86
|
+
const headers: HeadersInit = {};
|
|
87
|
+
|
|
88
|
+
if (key) {
|
|
89
|
+
headers.Authorization = `token ${key}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const response = await fetch(`https://api.github.com/repos/${repoName}`, {
|
|
93
|
+
headers,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (response.status === 403) {
|
|
97
|
+
return {stargazerTimes: [], repoImage: '', status: 'rate-limit'};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
return {stargazerTimes: [], repoImage: '', status: 'error'};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const responseParsed = await response.json();
|
|
105
|
+
const pages = Math.ceil(responseParsed.stargazers_count / PER_PAGE);
|
|
106
|
+
const info = await getTimes(repoName, pages, headers);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
stargazerTimes: info.stargazerTimes,
|
|
110
|
+
repoImage: responseParsed.owner.avatar_url,
|
|
111
|
+
status: info.status,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const RENDER_URL = 'http://localhost:4000/render';
|
|
2
|
+
|
|
3
|
+
async function getResponse(body: string) {
|
|
4
|
+
return await fetch(RENDER_URL, {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
headers: {
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
},
|
|
9
|
+
body,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function POST(request: Request) {
|
|
14
|
+
const body = await request.json();
|
|
15
|
+
|
|
16
|
+
const response = await getResponse(JSON.stringify(body));
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
return new Response('Failed to render', {status: 500});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return new Response(response.body, {status: 200});
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {Metadata} from 'next';
|
|
2
|
+
import {Inter} from 'next/font/google';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
|
|
5
|
+
const inter = Inter({subsets: ['latin']});
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: 'Create Next App',
|
|
9
|
+
description: 'Generated by create next app',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function RootLayout({
|
|
13
|
+
children,
|
|
14
|
+
}: Readonly<{
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}>) {
|
|
17
|
+
return (
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head>
|
|
20
|
+
<link rel="stylesheet" href="http://localhost:4000/player/project.css" />
|
|
21
|
+
</head>
|
|
22
|
+
<body className={inter.className}>{children}</body>
|
|
23
|
+
</html>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {Player} from '@revideo/player-react';
|
|
4
|
+
import {getGithubRepositoryInfo} from './actions';
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {LoaderCircle} from 'lucide-react';
|
|
7
|
+
import {parseStream} from '../utils/parse';
|
|
8
|
+
|
|
9
|
+
function Button({
|
|
10
|
+
children,
|
|
11
|
+
loading,
|
|
12
|
+
onClick,
|
|
13
|
+
}: {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
loading: boolean;
|
|
16
|
+
onClick: () => void;
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
className="text-sm flex items-center gap-x-2 rounded-md p-2 bg-gray-200 text-gray-700 hover:bg-gray-300"
|
|
21
|
+
onClick={() => onClick()}
|
|
22
|
+
>
|
|
23
|
+
{loading && <LoaderCircle className="animate-spin h-4 w-4 text-gray-700" />}
|
|
24
|
+
{children}
|
|
25
|
+
</button>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function Home() {
|
|
30
|
+
const [repoName, setRepoName] = useState<string>('');
|
|
31
|
+
const [repoImage, setRepoImage] = useState<string | null>();
|
|
32
|
+
const [stargazerTimes, setStargazerTimes] = useState<number[]>([]);
|
|
33
|
+
|
|
34
|
+
const [loading, setLoading] = useState(false);
|
|
35
|
+
const [needsKey, setNeedsKey] = useState(false);
|
|
36
|
+
const [key, setKey] = useState('');
|
|
37
|
+
const [error, setError] = useState<string | null>();
|
|
38
|
+
|
|
39
|
+
const [progress, setProgress] = useState(0);
|
|
40
|
+
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get information about the repository from Github.
|
|
44
|
+
* @param repoName
|
|
45
|
+
* @param key
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
async function fetchInformation(repoName: `${string}/${string}`, key: string) {
|
|
49
|
+
setLoading(true);
|
|
50
|
+
const response = await getGithubRepositoryInfo(repoName, key ?? undefined);
|
|
51
|
+
setLoading(false);
|
|
52
|
+
|
|
53
|
+
if (response.status === 'rate-limit') {
|
|
54
|
+
setNeedsKey(true);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (response.status === 'error') {
|
|
59
|
+
setError('Failed to fetch repository information from Github.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setStargazerTimes(response.stargazerTimes);
|
|
64
|
+
setRepoImage(response.repoImage);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render the video.
|
|
69
|
+
*/
|
|
70
|
+
async function render() {
|
|
71
|
+
const res = await fetch('/api/render', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
variables: {
|
|
78
|
+
data: stargazerTimes,
|
|
79
|
+
repoName: repoName,
|
|
80
|
+
repoImage: repoImage,
|
|
81
|
+
},
|
|
82
|
+
streamProgress: true,
|
|
83
|
+
}),
|
|
84
|
+
}).catch((e) => console.log(e));
|
|
85
|
+
|
|
86
|
+
if (!res) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const downloadUrl = await parseStream(res.body!.getReader(), (p) => setProgress(p));
|
|
91
|
+
setDownloadUrl(downloadUrl);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<div className="m-auto p-12 max-w-7xl flex flex-col gap-y-4">
|
|
97
|
+
<div>
|
|
98
|
+
<div className="text-sm text-gray-700 mb-2">Repository</div>
|
|
99
|
+
<div className="flex gap-x-4 text-sm">
|
|
100
|
+
<input
|
|
101
|
+
className="flex-1 rounded-md p-2 bg-gray-200 focus:outline-none placeholder:text-gray-400"
|
|
102
|
+
placeholder="redotvideo/revideo"
|
|
103
|
+
value={repoName}
|
|
104
|
+
onChange={(e) => setRepoName(e.target.value)}
|
|
105
|
+
/>
|
|
106
|
+
{!needsKey && (
|
|
107
|
+
<Button
|
|
108
|
+
loading={loading}
|
|
109
|
+
onClick={() => fetchInformation(repoName as `${string}/${string}`, key)}
|
|
110
|
+
>
|
|
111
|
+
Fetch information
|
|
112
|
+
</Button>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
{needsKey && (
|
|
117
|
+
<div>
|
|
118
|
+
<div className="text-sm text-blue-600 mb-2">
|
|
119
|
+
You hit the Github API rate-limit. Please provide your own key. Requests to Github are
|
|
120
|
+
made directly and the key stays on your device.
|
|
121
|
+
</div>
|
|
122
|
+
<div className="flex gap-x-4 text-sm">
|
|
123
|
+
<input
|
|
124
|
+
className="flex-1 rounded-md p-2 bg-gray-200 focus:outline-none placeholder:text-gray-400"
|
|
125
|
+
placeholder="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
126
|
+
value={key}
|
|
127
|
+
onChange={(e) => setKey(e.target.value)}
|
|
128
|
+
/>
|
|
129
|
+
<Button
|
|
130
|
+
loading={loading}
|
|
131
|
+
onClick={() => fetchInformation(repoName as `${string}/${string}`, key)}
|
|
132
|
+
>
|
|
133
|
+
Fetch information
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
{error && <div className="text-sm text-red-600">{error}</div>}
|
|
139
|
+
<div>
|
|
140
|
+
<div className="rounded-lg overflow-hidden">
|
|
141
|
+
{/* You can find the scene code inside revideo/src/scenes/example.tsx */}
|
|
142
|
+
<Player
|
|
143
|
+
src="http://localhost:4000/player/project.js"
|
|
144
|
+
controls={true}
|
|
145
|
+
variables={{
|
|
146
|
+
data: stargazerTimes.length > 0 ? stargazerTimes : undefined,
|
|
147
|
+
repoName: repoName ? repoName : undefined,
|
|
148
|
+
repoImage: repoImage ? repoImage : undefined,
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<div className="flex gap-x-4">
|
|
154
|
+
{/* Progress bar */}
|
|
155
|
+
<div className="text-sm flex-1 bg-gray-100 rounded-md overflow-hidden">
|
|
156
|
+
<div
|
|
157
|
+
className="text-gray-600 bg-gray-400 h-full flex items-center px-4 transition-all transition-200"
|
|
158
|
+
style={{
|
|
159
|
+
width: `${Math.round(progress * 100)}%`,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
{Math.round(progress * 100)}%
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
{downloadUrl ? (
|
|
166
|
+
<a
|
|
167
|
+
href={downloadUrl}
|
|
168
|
+
download
|
|
169
|
+
className="text-sm flex items-center gap-x-2 rounded-md p-2 bg-green-200 text-gray-700 hover:bg-gray-300"
|
|
170
|
+
>
|
|
171
|
+
Download video
|
|
172
|
+
</a>
|
|
173
|
+
) : (
|
|
174
|
+
<Button onClick={() => render()} loading={false}>
|
|
175
|
+
Render video
|
|
176
|
+
</Button>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@radix-ui/react-navigation-menu": "^1.1.4",
|
|
13
|
+
"@revideo/player-react": "^0.3.5",
|
|
14
|
+
"class-variance-authority": "^0.7.0",
|
|
15
|
+
"lucide-react": "^0.378.0",
|
|
16
|
+
"next": "14.2.3",
|
|
17
|
+
"p-limit": "^3",
|
|
18
|
+
"react": "^18",
|
|
19
|
+
"react-dom": "^18",
|
|
20
|
+
"tailwind-merge": "^2.3.0",
|
|
21
|
+
"tailwindcss-animate": "^1.0.7"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20",
|
|
25
|
+
"@types/react": "^18",
|
|
26
|
+
"@types/react-dom": "^18",
|
|
27
|
+
"autoprefixer": "^10.4.19",
|
|
28
|
+
"eslint": "^8",
|
|
29
|
+
"eslint-config-next": "14.2.3",
|
|
30
|
+
"postcss": "^8",
|
|
31
|
+
"tailwindcss": "^3.4.3",
|
|
32
|
+
"typescript": "^5"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|