bibibi12345 commited on
Commit
40e4e54
·
0 Parent(s):

initialized

Browse files
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the backend requirements file and install dependencies
8
+ COPY backend/requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy the backend and frontend code into the container
12
+ COPY backend/ /app/backend
13
+ COPY frontend/ /app/frontend
14
+
15
+ # Expose the port the app runs on
16
+ EXPOSE 7860
17
+
18
+ # Run the application
19
+ CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: VEO3 Video Generator
3
+ emoji: 📹
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # VEO3 Video Generator
11
+
12
+ This is a simple web application to generate videos using Google's VEO3 models via a Vertex Express Mode Key loophole.
13
+
14
+ ## How to use
15
+
16
+ 1. Enter your Vertex Express Key.
17
+ 2. Enter a text prompt.
18
+ 3. Optionally, upload an image or video.
19
+ 4. Select the desired parameters.
20
+ 5. Click "Generate Video".
backend/main.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uvicorn
2
+ from fastapi import FastAPI, File, UploadFile, Form, HTTPException
3
+ from fastapi.responses import FileResponse
4
+ from fastapi.staticfiles import StaticFiles
5
+ from typing import Optional
6
+ import services.vertex_service as vertex_service
7
+ import services.video_service as video_service
8
+ import os
9
+
10
+ app = FastAPI()
11
+
12
+ app.mount("/", StaticFiles(directory="frontend", html=True), name="static")
13
+
14
+ @app.post("/api/generate_video")
15
+ async def generate_video(
16
+ api_key: str = Form(...),
17
+ prompt: str = Form(...),
18
+ model: str = Form(...),
19
+ aspect_ratio: str = Form(...),
20
+ duration: int = Form(...),
21
+ resolution: str = Form(...),
22
+ generate_audio: bool = Form(...),
23
+ image: Optional[UploadFile] = File(None),
24
+ video: Optional[UploadFile] = File(None),
25
+ ):
26
+ try:
27
+ project_id = await vertex_service.get_project_id(api_key)
28
+
29
+ params = {
30
+ "prompt": prompt,
31
+ "model": model,
32
+ "aspectRatio": aspect_ratio,
33
+ "durationSeconds": duration,
34
+ "resolution": resolution,
35
+ "generateAudio": generate_audio,
36
+ }
37
+
38
+ if image:
39
+ params["image"] = await image.read()
40
+ params["image_mime_type"] = image.content_type
41
+
42
+ if video:
43
+ params["video"] = await video.read()
44
+ params["video_mime_type"] = video.content_type
45
+
46
+ operation_name = await vertex_service.start_video_generation(project_id, api_key, params)
47
+ video_data = await vertex_service.poll_video_status(operation_name, api_key)
48
+
49
+ video_path = await video_service.save_video(video_data)
50
+
51
+ return {"video_url": f"/api/video/{os.path.basename(video_path)}"}
52
+ except Exception as e:
53
+ raise HTTPException(status_code=500, detail=str(e))
54
+
55
+ @app.get("/api/video/{video_id}")
56
+ async def get_video(video_id: str):
57
+ video_path = os.path.join(video_service.VIDEO_DIR, video_id)
58
+ if os.path.exists(video_path):
59
+ return FileResponse(video_path)
60
+ raise HTTPException(status_code=404, detail="Video not found")
61
+
62
+ if __name__ == "__main__":
63
+ uvicorn.run(app, host="0.0.0.0", port=8000)
backend/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-multipart
4
+ requests
5
+ aiohttp
6
+ python-dotenv
backend/services/vertex_service.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import asyncio
3
+ import base64
4
+ import re
5
+ from typing import Dict, Any
6
+
7
+ # A simple in-memory cache for project IDs
8
+ project_id_cache = {}
9
+
10
+ async def get_project_id(api_key: str) -> str:
11
+ if api_key in project_id_cache:
12
+ return project_id_cache[api_key]
13
+
14
+ url = f"https://aiplatform.googleapis.com/v1/publishers/google/models/gemini-2.6:streamGenerateContent?key={api_key}"
15
+ headers = {"Content-Type": "application/json"}
16
+ data = {}
17
+
18
+ async with aiohttp.ClientSession() as session:
19
+ async with session.post(url, headers=headers, json=data) as response:
20
+ error_data = await response.json()
21
+ message = error_data[0]["error"]["message"]
22
+ match = re.search(r"projects/(\d+)/", message)
23
+ if match:
24
+ project_id = match.group(1)
25
+ project_id_cache[api_key] = project_id
26
+ return project_id
27
+ else:
28
+ raise Exception("Could not extract project ID")
29
+
30
+ async def start_video_generation(project_id: str, api_key: str, params: Dict[str, Any]) -> str:
31
+ location = "us-central1" # Or make this a parameter
32
+ model_id = params.pop("model")
33
+ url = f"https://{location}-aiplatform.googleapis.com/v1/projects/{project_id}/locations/{location}/publishers/google/models/{model_id}:predictLongRunning?key={api_key}"
34
+
35
+ instances = [{"prompt": params.get("prompt")}]
36
+ if "image" in params:
37
+ instances[0]["image"] = {
38
+ "bytesBase64Encoded": base64.b64encode(params["image"]).decode("utf-8"),
39
+ "mimeType": params["image_mime_type"]
40
+ }
41
+ if "video" in params:
42
+ instances[0]["video"] = {
43
+ "bytesBase64Encoded": base64.b64encode(params["video"]).decode("utf-8"),
44
+ "mimeType": params["video_mime_type"]
45
+ }
46
+
47
+ payload = {
48
+ "instances": instances,
49
+ "parameters": {
50
+ "aspectRatio": params.get("aspectRatio"),
51
+ "durationSeconds": params.get("durationSeconds"),
52
+ "resolution": params.get("resolution"),
53
+ "generateAudio": params.get("generateAudio"),
54
+ }
55
+ }
56
+
57
+ async with aiohttp.ClientSession() as session:
58
+ async with session.post(url, json=payload) as response:
59
+ response.raise_for_status()
60
+ data = await response.json()
61
+ return data["name"]
62
+
63
+ async def poll_video_status(operation_name: str, api_key: str) -> Dict[str, Any]:
64
+ location = "us-central1"
65
+ model_id = operation_name.split("/")[5]
66
+ url = f"https://{location}-aiplatform.googleapis.com/v1/projects/{operation_name.split('/')[1]}/locations/{location}/publishers/google/models/{model_id}:fetchPredictOperation?key={api_key}"
67
+
68
+ payload = {"operationName": operation_name}
69
+
70
+ async with aiohttp.ClientSession() as session:
71
+ while True:
72
+ async with session.post(url, json=payload) as response:
73
+ response.raise_for_status()
74
+ data = await response.json()
75
+ if data.get("done"):
76
+ return data["response"]
77
+ await asyncio.sleep(5) # Poll every 5 seconds
backend/services/video_service.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import base64
4
+ import aiohttp
5
+ from typing import Dict, Any
6
+
7
+ VIDEO_DIR = "generated_videos"
8
+ os.makedirs(VIDEO_DIR, exist_ok=True)
9
+
10
+ async def save_video(video_data: Dict[str, Any]) -> str:
11
+ video_filename = f"{uuid.uuid4()}.mp4"
12
+ video_path = os.path.join(VIDEO_DIR, video_filename)
13
+
14
+ if "gcsUri" in video_data["videos"][0]:
15
+ gcs_uri = video_data["videos"][0]["gcsUri"]
16
+ async with aiohttp.ClientSession() as session:
17
+ async with session.get(gcs_uri) as response:
18
+ response.raise_for_status()
19
+ with open(video_path, "wb") as f:
20
+ f.write(await response.read())
21
+ elif "bytesBase64Encoded" in video_data["videos"][0]:
22
+ video_bytes = base64.b64decode(video_data["videos"][0]["bytesBase64Encoded"])
23
+ with open(video_path, "wb") as f:
24
+ f.write(video_bytes)
25
+ else:
26
+ raise Exception("No video data found in response")
27
+
28
+ return video_path
frontend/app.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.getElementById("video-form").addEventListener("submit", async (event) => {
2
+ event.preventDefault();
3
+
4
+ const form = event.target;
5
+ const formData = new FormData();
6
+
7
+ formData.append("api_key", form["api-key"].value);
8
+ formData.append("prompt", form["prompt"].value);
9
+ formData.append("model", form["model"].value);
10
+ formData.append("aspect_ratio", form["aspect-ratio"].value);
11
+ formData.append("duration", form["duration"].value);
12
+ formData.append("resolution", form["resolution"].value);
13
+ formData.append("generate_audio", form["generate-audio"].checked);
14
+
15
+ const imageFile = form["image-prompt"].files[0];
16
+ if (imageFile) {
17
+ formData.append("image", imageFile);
18
+ }
19
+
20
+ const videoFile = form["video-prompt"].files[0];
21
+ if (videoFile) {
22
+ formData.append("video", videoFile);
23
+ }
24
+
25
+ const loader = document.getElementById("loader");
26
+ const videoElement = document.getElementById("generated-video");
27
+ const errorContainer = document.getElementById("error-container");
28
+
29
+ loader.classList.remove("hidden");
30
+ videoElement.style.display = "none";
31
+ errorContainer.classList.add("hidden");
32
+
33
+ try {
34
+ const response = await fetch("/api/generate_video", {
35
+ method: "POST",
36
+ body: formData,
37
+ });
38
+
39
+ if (!response.ok) {
40
+ const errorData = await response.json();
41
+ throw new Error(errorData.detail || "An unknown error occurred");
42
+ }
43
+
44
+ const data = await response.json();
45
+ videoElement.src = data.video_url;
46
+ videoElement.style.display = "block";
47
+ } catch (error) {
48
+ errorContainer.textContent = error.message;
49
+ errorContainer.classList.remove("hidden");
50
+ } finally {
51
+ loader.classList.add("hidden");
52
+ }
53
+ });
frontend/index.html ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>VEO3 Video Generator</title>
7
+ <link rel="stylesheet" href="/style.css">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1>VEO3 Video Generator</h1>
12
+ </header>
13
+ <main>
14
+ <form id="video-form">
15
+ <label for="api-key">Vertex Express Key:</label>
16
+ <input type="text" id="api-key" name="api-key" required>
17
+
18
+ <label for="prompt">Prompt:</label>
19
+ <textarea id="prompt" name="prompt" rows="4" required></textarea>
20
+
21
+ <label for="model">Model:</label>
22
+ <select id="model" name="model">
23
+ <option value="veo-2.0-generate-001">veo-2.0-generate-001</option>
24
+ <option value="veo-3.0-generate-001">veo-3.0-generate-001</option>
25
+ <option value="veo-3.0-fast-generate-001">veo-3.0-fast-generate-001</option>
26
+ <option value="veo-3.0-generate-preview" selected>veo-3.0-generate-preview</option>
27
+ <option value="veo-3.0-fast-generate-preview">veo-3.0-fast-generate-preview</option>
28
+ </select>
29
+
30
+ <label for="aspect-ratio">Aspect Ratio:</label>
31
+ <select id="aspect-ratio" name="aspect-ratio">
32
+ <option value="16:9">16:9</option>
33
+ <option value="9:16">9:16</option>
34
+ </select>
35
+
36
+ <label for="duration">Duration (seconds):</label>
37
+ <input type="number" id="duration" name="duration" value="8" min="5" max="8">
38
+
39
+ <label for="resolution">Resolution:</label>
40
+ <select id="resolution" name="resolution">
41
+ <option value="720p">720p</option>
42
+ <option value="1080p">1080p</option>
43
+ </select>
44
+
45
+ <label for="generate-audio">Generate Audio:</label>
46
+ <input type="checkbox" id="generate-audio" name="generate-audio" checked>
47
+
48
+ <label for="image-prompt">Image Prompt:</label>
49
+ <input type="file" id="image-prompt" name="image-prompt" accept="image/jpeg,image/png">
50
+
51
+ <label for="video-prompt">Video Prompt:</label>
52
+ <input type="file" id="video-prompt" name="video-prompt" accept="video/mp4">
53
+
54
+ <button type="submit">Generate Video</button>
55
+ </form>
56
+
57
+ <div id="video-container">
58
+ <div id="loader" class="hidden"></div>
59
+ <video id="generated-video" controls></video>
60
+ <div id="error-container" class="hidden"></div>
61
+ </div>
62
+ </main>
63
+ <script src="/app.js"></script>
64
+ </body>
65
+ </html>
frontend/style.css ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: sans-serif;
3
+ margin: 0;
4
+ background-color: #f4f4f4;
5
+ color: #333;
6
+ }
7
+
8
+ header {
9
+ background-color: #333;
10
+ color: #fff;
11
+ padding: 1rem;
12
+ text-align: center;
13
+ }
14
+
15
+ main {
16
+ max-width: 800px;
17
+ margin: 2rem auto;
18
+ padding: 1rem;
19
+ background-color: #fff;
20
+ border-radius: 5px;
21
+ }
22
+
23
+ form {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: 1rem;
27
+ }
28
+
29
+ label {
30
+ font-weight: bold;
31
+ }
32
+
33
+ input[type="text"],
34
+ textarea,
35
+ select {
36
+ padding: 0.5rem;
37
+ border: 1px solid #ccc;
38
+ border-radius: 3px;
39
+ }
40
+
41
+ button {
42
+ padding: 0.75rem;
43
+ background-color: #333;
44
+ color: #fff;
45
+ border: none;
46
+ border-radius: 3px;
47
+ cursor: pointer;
48
+ }
49
+
50
+ button:hover {
51
+ background-color: #555;
52
+ }
53
+
54
+ #video-container {
55
+ margin-top: 2rem;
56
+ text-align: center;
57
+ }
58
+
59
+ #generated-video {
60
+ max-width: 100%;
61
+ display: none;
62
+ }
63
+
64
+ .hidden {
65
+ display: none;
66
+ }
67
+
68
+ #loader {
69
+ border: 8px solid #f3f3f3;
70
+ border-top: 8px solid #3498db;
71
+ border-radius: 50%;
72
+ width: 50px;
73
+ height: 50px;
74
+ animation: spin 1s linear infinite;
75
+ margin: 0 auto;
76
+ }
77
+
78
+ @keyframes spin {
79
+ 0% { transform: rotate(0deg); }
80
+ 100% { transform: rotate(360deg); }
81
+ }
82
+
83
+ #error-container {
84
+ color: red;
85
+ margin-top: 1rem;
86
+ }