ginipick commited on
Commit
b16eb58
ยท
verified ยท
1 Parent(s): 19aa20c

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -330
index.html DELETED
@@ -1,330 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Video, Upload, Loader2, Download, Settings } from 'lucide-react';
3
-
4
- const VideoGenerator = () => {
5
- const [mode, setMode] = useState('text-to-video');
6
- const [prompt, setPrompt] = useState('');
7
- const [imageFile, setImageFile] = useState(null);
8
- const [imagePreview, setImagePreview] = useState('');
9
- const [aspectRatio, setAspectRatio] = useState('16:9');
10
- const [seed, setSeed] = useState(42);
11
- const [isGenerating, setIsGenerating] = useState(false);
12
- const [videoUrl, setVideoUrl] = useState('');
13
- const [error, setError] = useState('');
14
- const [apiKey, setApiKey] = useState('');
15
- const [showApiKeyInput, setShowApiKeyInput] = useState(false);
16
-
17
- const aspectRatioOptions = [
18
- { value: '16:9', label: '16:9', description: '(YouTube, ์ผ๋ฐ˜ ๋™์˜์ƒ)' },
19
- { value: '4:3', label: '4:3', description: '(์ „ํ†ต์ ์ธ TV ํ˜•์‹)' },
20
- { value: '1:1', label: '1:1', description: '(Instagram ํ”ผ๋“œ)' },
21
- { value: '3:4', label: '3:4', description: '(Instagram ํฌํŠธ๋ ˆ์ดํŠธ)' },
22
- { value: '9:16', label: '9:16', description: '(Instagram ๋ฆด์Šค, TikTok)' },
23
- { value: '21:9', label: '21:9', description: '(์‹œ๋„ค๋งˆํ‹ฑ ์™€์ด๋“œ)' },
24
- { value: '9:21', label: '9:21', description: '(์šธํŠธ๋ผ ์„ธ๋กœํ˜•)' }
25
- ];
26
-
27
- const handleImageUpload = (e) => {
28
- const file = e.target.files[0];
29
- if (file) {
30
- setImageFile(file);
31
- const reader = new FileReader();
32
- reader.onloadend = () => {
33
- setImagePreview(reader.result);
34
- };
35
- reader.readAsDataURL(file);
36
- }
37
- };
38
-
39
- const generateVideo = async () => {
40
- if (!prompt.trim()) {
41
- setError('ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.');
42
- return;
43
- }
44
-
45
- if (mode === 'image-to-video' && !imageFile) {
46
- setError('์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.');
47
- return;
48
- }
49
-
50
- setIsGenerating(true);
51
- setError('');
52
- setVideoUrl('');
53
-
54
- try {
55
- // API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
56
- const token = apiKey || process.env.RAPI_TOKEN;
57
-
58
- if (!token) {
59
- throw new Error('Replicate API ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์„ค์ •์—์„œ API ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.');
60
- }
61
-
62
- // Python ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค
63
- const pythonCode = `
64
- import os
65
- import replicate
66
-
67
- # API ํ† ํฐ ์„ค์ •
68
- os.environ["REPLICATE_API_TOKEN"] = "${token}"
69
-
70
- input = {
71
- "prompt": "${prompt}",
72
- "duration": 5,
73
- "resolution": "480p",
74
- "aspect_ratio": "${aspectRatio}",
75
- "seed": ${seed}${mode === 'image-to-video' ? ',\n "image": "' + imagePreview + '"' : ''}
76
- }
77
-
78
- output = replicate.run(
79
- "bytedance/seedance-1-lite",
80
- input=input
81
- )
82
-
83
- # ๋น„๋””์˜ค URL ๋ฐ˜ํ™˜
84
- print(output)
85
- `;
86
-
87
- // ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ๋ฐฑ์—”๋“œ API๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
88
- console.log('์ƒ์„ฑํ•  Python ์ฝ”๋“œ:', pythonCode);
89
-
90
- // ๋ฐ๋ชจ๋ฅผ ์œ„ํ•œ ๊ฐ€์งœ ์‘๋‹ต
91
- setTimeout(() => {
92
- setVideoUrl('https://example.com/generated-video.mp4');
93
- setIsGenerating(false);
94
- }, 3000);
95
-
96
- } catch (err) {
97
- setError(err.message);
98
- setIsGenerating(false);
99
- }
100
- };
101
-
102
- return (
103
- <div className="min-h-screen bg-gray-900 text-white p-8">
104
- <div className="max-w-4xl mx-auto">
105
- <h1 className="text-4xl font-bold mb-8 text-center">AI Video Generator</h1>
106
-
107
- {/* API ํ‚ค ์„ค์ • */}
108
- <div className="mb-6">
109
- <button
110
- onClick={() => setShowApiKeyInput(!showApiKeyInput)}
111
- className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors"
112
- >
113
- <Settings size={20} />
114
- API ์„ค์ •
115
- </button>
116
-
117
- {showApiKeyInput && (
118
- <div className="mt-2">
119
- <input
120
- type="password"
121
- placeholder="Replicate API ํ† ํฐ ์ž…๋ ฅ (์„ ํƒ์‚ฌํ•ญ)"
122
- value={apiKey}
123
- onChange={(e) => setApiKey(e.target.value)}
124
- className="w-full p-2 bg-gray-800 rounded border border-gray-700 focus:border-blue-500 outline-none"
125
- />
126
- <p className="text-xs text-gray-500 mt-1">
127
- ํ™˜๊ฒฝ๋ณ€์ˆ˜ RAPI_TOKEN์ด ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ์—ฌ๊ธฐ์— ์ž…๋ ฅํ•˜์„ธ์š”.
128
- </p>
129
- </div>
130
- )}
131
- </div>
132
-
133
- {/* ๋ชจ๋“œ ์„ ํƒ */}
134
- <div className="mb-8">
135
- <h2 className="text-xl font-semibold mb-4">์ƒ์„ฑ ๋ชจ๋“œ ์„ ํƒ</h2>
136
- <div className="grid grid-cols-2 gap-4">
137
- <button
138
- onClick={() => setMode('text-to-video')}
139
- className={`p-4 rounded-lg border-2 transition-all ${
140
- mode === 'text-to-video'
141
- ? 'border-blue-500 bg-blue-500/20'
142
- : 'border-gray-700 hover:border-gray-600'
143
- }`}
144
- >
145
- <Video className="mb-2" size={32} />
146
- <h3 className="font-semibold">ํ…์Šค๏ฟฝ๏ฟฝ to ๋น„๋””์˜ค</h3>
147
- <p className="text-sm text-gray-400">ํ…์ŠคํŠธ ์„ค๋ช…์œผ๋กœ ๋น„๋””์˜ค ์ƒ์„ฑ</p>
148
- </button>
149
-
150
- <button
151
- onClick={() => setMode('image-to-video')}
152
- className={`p-4 rounded-lg border-2 transition-all ${
153
- mode === 'image-to-video'
154
- ? 'border-blue-500 bg-blue-500/20'
155
- : 'border-gray-700 hover:border-gray-600'
156
- }`}
157
- >
158
- <Upload className="mb-2" size={32} />
159
- <h3 className="font-semibold">์ด๋ฏธ์ง€ to ๋น„๋””์˜ค</h3>
160
- <p className="text-sm text-gray-400">์ด๋ฏธ์ง€๋ฅผ ์›€์ง์ด๋Š” ๋น„๋””์˜ค๋กœ ๋ณ€ํ™˜</p>
161
- </button>
162
- </div>
163
- </div>
164
-
165
- {/* ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (์ด๋ฏธ์ง€ to ๋น„๋””์˜ค ๋ชจ๋“œ) */}
166
- {mode === 'image-to-video' && (
167
- <div className="mb-6">
168
- <h2 className="text-xl font-semibold mb-4">์ด๋ฏธ์ง€ ์—…๋กœ๋“œ</h2>
169
- <div className="border-2 border-dashed border-gray-700 rounded-lg p-8 text-center">
170
- {imagePreview ? (
171
- <div className="space-y-4">
172
- <img src={imagePreview} alt="Preview" className="max-h-64 mx-auto rounded" />
173
- <button
174
- onClick={() => {
175
- setImageFile(null);
176
- setImagePreview('');
177
- }}
178
- className="text-red-400 hover:text-red-300"
179
- >
180
- ์ด๋ฏธ์ง€ ์ œ๊ฑฐ
181
- </button>
182
- </div>
183
- ) : (
184
- <>
185
- <Upload size={48} className="mx-auto mb-4 text-gray-600" />
186
- <input
187
- type="file"
188
- accept="image/*"
189
- onChange={handleImageUpload}
190
- className="hidden"
191
- id="image-upload"
192
- />
193
- <label
194
- htmlFor="image-upload"
195
- className="cursor-pointer text-blue-400 hover:text-blue-300"
196
- >
197
- ์ด๋ฏธ์ง€ ์„ ํƒ
198
- </label>
199
- </>
200
- )}
201
- </div>
202
- </div>
203
- )}
204
-
205
- {/* ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ */}
206
- <div className="mb-6">
207
- <h2 className="text-xl font-semibold mb-4">
208
- {mode === 'text-to-video' ? '๋น„๋””์˜ค ์„ค๋ช…' : '์˜์ƒ ์ƒ์„ฑ ํ”„๋กฌํ”„ํŠธ'}
209
- </h2>
210
- <textarea
211
- value={prompt}
212
- onChange={(e) => setPrompt(e.target.value)}
213
- placeholder={
214
- mode === 'text-to-video'
215
- ? "์ƒ์„ฑํ•  ๋น„๋””์˜ค๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”. ์˜ˆ: The sun rises slowly between tall buildings..."
216
- : "์ด๋ฏธ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ์›€์ง์ด๊ฒŒ ํ• ์ง€ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”. ์˜ˆ: Camera slowly zooms in while clouds move..."
217
- }
218
- className="w-full h-32 p-4 bg-gray-800 rounded-lg border border-gray-700 focus:border-blue-500 outline-none resize-none"
219
- />
220
- </div>
221
-
222
- {/* ์„ค์ • ์˜ต์…˜ */}
223
- <div className="mb-8 space-y-4">
224
- <h2 className="text-xl font-semibold mb-4">์„ค์ •</h2>
225
-
226
- {/* ํ™”๋ฉด ๋น„์œจ ์„ ํƒ */}
227
- <div>
228
- <label className="block text-sm font-medium mb-2">ํ™”๋ฉด ๋น„์œจ</label>
229
- <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
230
- {aspectRatioOptions.map((option) => (
231
- <button
232
- key={option.value}
233
- onClick={() => setAspectRatio(option.value)}
234
- className={`p-3 rounded border transition-all ${
235
- aspectRatio === option.value
236
- ? 'border-blue-500 bg-blue-500/20'
237
- : 'border-gray-700 hover:border-gray-600'
238
- }`}
239
- >
240
- <div className="font-semibold">{option.label}</div>
241
- <div className="text-xs text-gray-400">{option.description}</div>
242
- </button>
243
- ))}
244
- </div>
245
- </div>
246
-
247
- {/* Seed ์„ค์ • */}
248
- <div>
249
- <label className="block text-sm font-medium mb-2">Seed (๋žœ๋ค ์‹œ๋“œ)</label>
250
- <input
251
- type="number"
252
- value={seed}
253
- onChange={(e) => setSeed(parseInt(e.target.value) || 0)}
254
- className="w-full p-2 bg-gray-800 rounded border border-gray-700 focus:border-blue-500 outline-none"
255
- />
256
- <p className="text-xs text-gray-500 mt-1">๋™์ผํ•œ ์‹œ๋“œ๊ฐ’์œผ๋กœ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</p>
257
- </div>
258
-
259
- {/* ๊ณ ์ • ์„ค์ • ํ‘œ์‹œ */}
260
- <div className="text-sm text-gray-400">
261
- <p>โ€ข ํ•ด์ƒ๋„: 480p (๊ณ ์ •)</p>
262
- <p>โ€ข ์žฌ์ƒ ์‹œ๊ฐ„: 5์ดˆ (๊ณ ์ •)</p>
263
- </div>
264
- </div>
265
-
266
- {/* ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ */}
267
- {error && (
268
- <div className="mb-4 p-4 bg-red-500/20 border border-red-500 rounded-lg text-red-400">
269
- {error}
270
- </div>
271
- )}
272
-
273
- {/* ์ƒ์„ฑ ๋ฒ„ํŠผ */}
274
- <button
275
- onClick={generateVideo}
276
- disabled={isGenerating}
277
- className="w-full py-4 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-700 rounded-lg font-semibold transition-colors flex items-center justify-center gap-2"
278
- >
279
- {isGenerating ? (
280
- <>
281
- <Loader2 className="animate-spin" size={20} />
282
- ๋น„๋””์˜ค ์ƒ์„ฑ ์ค‘...
283
- </>
284
- ) : (
285
- <>
286
- <Video size={20} />
287
- ๋น„๋””์˜ค ์ƒ์„ฑ
288
- </>
289
- )}
290
- </button>
291
-
292
- {/* ๊ฒฐ๊ณผ ํ‘œ์‹œ */}
293
- {videoUrl && (
294
- <div className="mt-8 p-6 bg-gray-800 rounded-lg">
295
- <h3 className="text-xl font-semibold mb-4">์ƒ์„ฑ๋œ ๋น„๋””์˜ค</h3>
296
- <video
297
- src={videoUrl}
298
- controls
299
- className="w-full rounded mb-4"
300
- />
301
- <a
302
- href={videoUrl}
303
- download="generated-video.mp4"
304
- className="inline-flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 rounded transition-colors"
305
- >
306
- <Download size={20} />
307
- ๋น„๋””์˜ค ๋‹ค์šด๋กœ๋“œ
308
- </a>
309
- </div>
310
- )}
311
-
312
- {/* ๊ตฌํ˜„ ๋…ธํŠธ */}
313
- <div className="mt-12 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
314
- <h4 className="font-semibold text-yellow-400 mb-2">๊ตฌํ˜„ ๋…ธํŠธ</h4>
315
- <p className="text-sm text-gray-300">
316
- ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋ฐ๋ชจ์šฉ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ž‘๋™ํ•˜๋ ค๋ฉด:
317
- </p>
318
- <ul className="text-sm text-gray-300 mt-2 list-disc list-inside space-y-1">
319
- <li>๋ฐฑ์—”๋“œ ์„œ๋ฒ„์—์„œ Replicate API๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค</li>
320
- <li>ํ™˜๊ฒฝ๋ณ€์ˆ˜ RAPI_TOKEN์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ API ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค</li>
321
- <li>bytedance/seedance-1-lite ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค</li>
322
- <li>์ƒ์„ฑ๋œ ๋น„๋””์˜ค๋Š” ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌ ํ›„ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค</li>
323
- </ul>
324
- </div>
325
- </div>
326
- </div>
327
- );
328
- };
329
-
330
- export default VideoGenerator;