Update app.py
Browse files
app.py
CHANGED
@@ -12,16 +12,13 @@ from huggingface_hub import login
|
|
12 |
|
13 |
# --- 1. Configuration and Authentication ---
|
14 |
|
15 |
-
#
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
PREDICT_URL = f"https://{API_ENDPOINT}/v1/projects/{GCP_PROJECT_ID}/locations/{GCP_LOCATION}/publishers/google/models/{MODEL_ID}:predictLongRunning"
|
21 |
-
FETCH_URL = f"https://{API_ENDPOINT}/v1/projects/{GCP_PROJECT_ID}/locations/{GCP_LOCATION}/publishers/google/models/{MODEL_ID}:fetchPredictOperation"
|
22 |
|
23 |
-
|
24 |
-
# --- Authentication Block ---
|
25 |
|
26 |
# Part A: Hugging Face Hub Authentication
|
27 |
hf_token = os.environ.get("HF_TOKEN")
|
@@ -33,26 +30,42 @@ else:
|
|
33 |
|
34 |
# Part B: Google Cloud Authentication
|
35 |
creds_json_str = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS_JSON")
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
def generate_video(prompt):
|
39 |
-
raise gr.Error(
|
40 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
with open("gcp_creds.json", "w") as f:
|
42 |
f.write(creds_json_str)
|
43 |
|
44 |
SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
|
45 |
credentials, _ = google.auth.load_credentials_from_file("gcp_creds.json", scopes=SCOPES)
|
46 |
-
print("GCP credentials loaded successfully.")
|
47 |
-
|
48 |
|
49 |
def get_access_token():
|
50 |
auth_req = google.auth.transport.requests.Request()
|
51 |
credentials.refresh(auth_req)
|
52 |
return credentials.token
|
53 |
|
54 |
-
# --- 2. Core Video Generation Logic
|
55 |
-
|
56 |
def generate_video(prompt: str):
|
57 |
if not prompt:
|
58 |
raise gr.Error("Prompt cannot be empty.")
|
@@ -65,7 +78,6 @@ else:
|
|
65 |
"Authorization": f"Bearer {access_token}",
|
66 |
"Content-Type": "application/json",
|
67 |
}
|
68 |
-
|
69 |
payload = {
|
70 |
"instances": [{"prompt": prompt}],
|
71 |
"parameters": {
|
@@ -74,92 +86,55 @@ else:
|
|
74 |
"includeRaiReason": True, "generateAudio": True,
|
75 |
}
|
76 |
}
|
77 |
-
|
78 |
response = requests.post(PREDICT_URL, headers=headers, json=payload)
|
79 |
response.raise_for_status()
|
80 |
-
|
81 |
operation_name = response.json()["name"]
|
82 |
print(f"Successfully submitted job. Operation Name: {operation_name}")
|
83 |
|
84 |
MAX_POLL_ATTEMPTS = 60
|
85 |
for i in range(MAX_POLL_ATTEMPTS):
|
86 |
-
|
87 |
-
yield status_message, None
|
88 |
-
|
89 |
access_token = get_access_token()
|
90 |
headers["Authorization"] = f"Bearer {access_token}"
|
91 |
-
|
92 |
fetch_payload = {"operationName": operation_name}
|
93 |
poll_response = requests.post(FETCH_URL, headers=headers, json=fetch_payload)
|
94 |
poll_response.raise_for_status()
|
95 |
-
|
96 |
poll_result = poll_response.json()
|
97 |
|
98 |
if poll_result.get("done"):
|
99 |
print("Job finished successfully.")
|
100 |
-
print(f"Full successful response payload: {json.dumps(poll_result, indent=2)}")
|
101 |
-
|
102 |
response_data = poll_result.get("response", {})
|
103 |
-
|
104 |
-
# <<< FIX: Changed "predictions" to "videos" to match the actual API response >>>
|
105 |
if "videos" in response_data and response_data["videos"]:
|
106 |
video_base64 = response_data["videos"][0]["bytesBase64Encoded"]
|
107 |
video_bytes = base64.b64decode(video_base64)
|
108 |
-
|
109 |
temp_video_path = "generated_video.mp4"
|
110 |
with open(temp_video_path, "wb") as f:
|
111 |
f.write(video_bytes)
|
112 |
-
|
113 |
-
yield "Status: Done! Video generated successfully.", temp_video_path
|
114 |
return
|
115 |
else:
|
116 |
-
|
117 |
-
print(f"ERROR: {error_message}")
|
118 |
-
raise gr.Error(error_message)
|
119 |
-
|
120 |
time.sleep(10)
|
121 |
-
|
122 |
-
raise gr.Error("Operation timed out after several minutes.")
|
123 |
-
|
124 |
-
except requests.exceptions.HTTPError as e:
|
125 |
-
print(f"HTTP Error: {e.response.text}")
|
126 |
-
raise gr.Error(f"API Error: {e.response.status_code}. Details: {e.response.text}")
|
127 |
except Exception as e:
|
128 |
-
print(f"An error occurred
|
129 |
raise gr.Error(str(e))
|
130 |
|
131 |
-
|
132 |
-
# --- 3. Gradio User Interface (Unchanged) ---
|
133 |
-
|
134 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
135 |
gr.Markdown("# 🎬 Vertex AI VEO Video Generator")
|
136 |
-
gr.Markdown(
|
137 |
-
"Generate short videos from a text prompt using Google's VEO model. "
|
138 |
-
"Generation can take several minutes. Please be patient."
|
139 |
-
)
|
140 |
-
|
141 |
with gr.Row():
|
142 |
with gr.Column(scale=1):
|
143 |
-
prompt_input = gr.Textbox(
|
144 |
-
label="Prompt",
|
145 |
-
placeholder="A majestic lion roaming the savanna at sunrise, cinematic 4K.",
|
146 |
-
lines=3
|
147 |
-
)
|
148 |
submit_button = gr.Button("Generate Video", variant="primary")
|
149 |
-
|
150 |
with gr.Column(scale=1):
|
151 |
status_output = gr.Markdown("Status: Ready")
|
152 |
video_output = gr.Video(label="Generated Video", interactive=False)
|
153 |
-
|
154 |
gr.Examples(
|
155 |
-
|
156 |
-
"A high-speed drone shot flying through a futuristic city with flying vehicles.",
|
157 |
-
"A raccoon happily eating popcorn in a movie theater, cinematic lighting.",
|
158 |
-
"A beautiful time-lapse of a flower blooming, from bud to full blossom, ultra-realistic.",
|
159 |
-
],
|
160 |
inputs=prompt_input,
|
161 |
)
|
162 |
-
|
163 |
submit_button.click(
|
164 |
fn=generate_video,
|
165 |
inputs=prompt_input,
|
|
|
12 |
|
13 |
# --- 1. Configuration and Authentication ---
|
14 |
|
15 |
+
# <<< START: CODE UPDATE >>>
|
16 |
+
# Load configuration from Hugging Face Secrets instead of hardcoding.
|
17 |
+
GCP_PROJECT_ID = os.environ.get("GCP_PROJECT_ID")
|
18 |
+
GCP_LOCATION = os.environ.get("GCP_LOCATION")
|
19 |
+
# <<< END: CODE UPDATE >>>
|
|
|
|
|
20 |
|
21 |
+
# --- Authentication and Sanity Checks Block ---
|
|
|
22 |
|
23 |
# Part A: Hugging Face Hub Authentication
|
24 |
hf_token = os.environ.get("HF_TOKEN")
|
|
|
30 |
|
31 |
# Part B: Google Cloud Authentication
|
32 |
creds_json_str = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS_JSON")
|
33 |
+
|
34 |
+
# Check if all necessary secrets are loaded
|
35 |
+
if not all([GCP_PROJECT_ID, GCP_LOCATION, creds_json_str]):
|
36 |
+
# This block runs if any of the crucial secrets are missing.
|
37 |
+
missing_secrets = []
|
38 |
+
if not GCP_PROJECT_ID: missing_secrets.append("GCP_PROJECT_ID")
|
39 |
+
if not GCP_LOCATION: missing_secrets.append("GCP_LOCATION")
|
40 |
+
if not creds_json_str: missing_secrets.append("GOOGLE_APPLICATION_CREDENTIALS_JSON")
|
41 |
+
|
42 |
+
error_message = f"FATAL: The following required secrets are missing: {', '.join(missing_secrets)}. Please set them in the Space settings."
|
43 |
+
print(error_message)
|
44 |
+
# Define a dummy function to show a clear error in the UI.
|
45 |
def generate_video(prompt):
|
46 |
+
raise gr.Error(error_message)
|
47 |
else:
|
48 |
+
# This block runs only if all secrets are present.
|
49 |
+
print("All required secrets (GCP Project, Location, Credentials, HF Token) are loaded.")
|
50 |
+
|
51 |
+
# Construct API URLs now that we have the project and location
|
52 |
+
MODEL_ID = "veo-3.0-generate-preview"
|
53 |
+
API_ENDPOINT = f"{GCP_LOCATION}-aiplatform.googleapis.com"
|
54 |
+
PREDICT_URL = f"https://{API_ENDPOINT}/v1/projects/{GCP_PROJECT_ID}/locations/{GCP_LOCATION}/publishers/google/models/{MODEL_ID}:predictLongRunning"
|
55 |
+
FETCH_URL = f"https://{API_ENDPOINT}/v1/projects/{GCP_PROJECT_ID}/locations/{GCP_LOCATION}/publishers/google/models/{MODEL_ID}:fetchPredictOperation"
|
56 |
+
|
57 |
with open("gcp_creds.json", "w") as f:
|
58 |
f.write(creds_json_str)
|
59 |
|
60 |
SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
|
61 |
credentials, _ = google.auth.load_credentials_from_file("gcp_creds.json", scopes=SCOPES)
|
|
|
|
|
62 |
|
63 |
def get_access_token():
|
64 |
auth_req = google.auth.transport.requests.Request()
|
65 |
credentials.refresh(auth_req)
|
66 |
return credentials.token
|
67 |
|
68 |
+
# --- 2. Core Video Generation Logic ---
|
|
|
69 |
def generate_video(prompt: str):
|
70 |
if not prompt:
|
71 |
raise gr.Error("Prompt cannot be empty.")
|
|
|
78 |
"Authorization": f"Bearer {access_token}",
|
79 |
"Content-Type": "application/json",
|
80 |
}
|
|
|
81 |
payload = {
|
82 |
"instances": [{"prompt": prompt}],
|
83 |
"parameters": {
|
|
|
86 |
"includeRaiReason": True, "generateAudio": True,
|
87 |
}
|
88 |
}
|
|
|
89 |
response = requests.post(PREDICT_URL, headers=headers, json=payload)
|
90 |
response.raise_for_status()
|
|
|
91 |
operation_name = response.json()["name"]
|
92 |
print(f"Successfully submitted job. Operation Name: {operation_name}")
|
93 |
|
94 |
MAX_POLL_ATTEMPTS = 60
|
95 |
for i in range(MAX_POLL_ATTEMPTS):
|
96 |
+
yield f"Status: Polling for result (Attempt {i+1}/{MAX_POLL_ATTEMPTS})...", None
|
|
|
|
|
97 |
access_token = get_access_token()
|
98 |
headers["Authorization"] = f"Bearer {access_token}"
|
|
|
99 |
fetch_payload = {"operationName": operation_name}
|
100 |
poll_response = requests.post(FETCH_URL, headers=headers, json=fetch_payload)
|
101 |
poll_response.raise_for_status()
|
|
|
102 |
poll_result = poll_response.json()
|
103 |
|
104 |
if poll_result.get("done"):
|
105 |
print("Job finished successfully.")
|
|
|
|
|
106 |
response_data = poll_result.get("response", {})
|
|
|
|
|
107 |
if "videos" in response_data and response_data["videos"]:
|
108 |
video_base64 = response_data["videos"][0]["bytesBase64Encoded"]
|
109 |
video_bytes = base64.b64decode(video_base64)
|
|
|
110 |
temp_video_path = "generated_video.mp4"
|
111 |
with open(temp_video_path, "wb") as f:
|
112 |
f.write(video_bytes)
|
113 |
+
yield "Status: Done! Video generated.", temp_video_path
|
|
|
114 |
return
|
115 |
else:
|
116 |
+
raise gr.Error("Video generation finished, but content was blocked or another issue occurred.")
|
|
|
|
|
|
|
117 |
time.sleep(10)
|
118 |
+
raise gr.Error("Operation timed out.")
|
|
|
|
|
|
|
|
|
|
|
119 |
except Exception as e:
|
120 |
+
print(f"An error occurred: {e}")
|
121 |
raise gr.Error(str(e))
|
122 |
|
123 |
+
# --- 3. Gradio User Interface ---
|
|
|
|
|
124 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
125 |
gr.Markdown("# 🎬 Vertex AI VEO Video Generator")
|
126 |
+
gr.Markdown("Generate short videos from a text prompt using Google's VEO model.")
|
|
|
|
|
|
|
|
|
127 |
with gr.Row():
|
128 |
with gr.Column(scale=1):
|
129 |
+
prompt_input = gr.Textbox(label="Prompt", placeholder="A majestic lion...", lines=3)
|
|
|
|
|
|
|
|
|
130 |
submit_button = gr.Button("Generate Video", variant="primary")
|
|
|
131 |
with gr.Column(scale=1):
|
132 |
status_output = gr.Markdown("Status: Ready")
|
133 |
video_output = gr.Video(label="Generated Video", interactive=False)
|
|
|
134 |
gr.Examples(
|
135 |
+
["A high-speed drone shot flying through a futuristic city with flying vehicles."],
|
|
|
|
|
|
|
|
|
136 |
inputs=prompt_input,
|
137 |
)
|
|
|
138 |
submit_button.click(
|
139 |
fn=generate_video,
|
140 |
inputs=prompt_input,
|