File size: 5,786 Bytes
0fea6b2 c1c563f 0fea6b2 987e891 c1c563f 0fea6b2 c1c563f 156c06c c1c563f 987e891 0fea6b2 156c06c 987e891 0fea6b2 987e891 0fea6b2 c1c563f 0fea6b2 c1c563f 0fea6b2 c1c563f 0fea6b2 c1c563f 0fea6b2 c1c563f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# app.py (API-Only Version)
import os
import gradio as gr
import requests
import json
import time
import base64
import google.auth
import google.auth.transport.requests
from huggingface_hub import login
# --- 1. Configuration and Authentication (Unchanged) ---
GCP_PROJECT_ID = os.environ.get("GCP_PROJECT_ID")
GCP_LOCATION = os.environ.get("GCP_LOCATION")
# --- Authentication and Sanity Checks Block (Unchanged) ---
hf_token = os.environ.get("HF_TOKEN")
if hf_token:
print("Hugging Face token found. Logging in.")
login(token=hf_token)
else:
print("WARNING: Hugging Face token ('HF_TOKEN') not found.")
creds_json_str = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS_JSON")
if not all([GCP_PROJECT_ID, GCP_LOCATION, creds_json_str]):
missing_secrets = [s for s, v in {"GCP_PROJECT_ID": GCP_PROJECT_ID, "GCP_LOCATION": GCP_LOCATION, "GOOGLE_APPLICATION_CREDENTIALS_JSON": creds_json_str}.items() if not v]
error_message = f"FATAL: Missing required secrets: {', '.join(missing_secrets)}."
print(error_message)
# This initial error will still be raised if the app can't start
raise RuntimeError(error_message)
print("All required secrets are loaded. Initializing API service.")
MODEL_ID = "veo-3.0-generate-preview"
API_ENDPOINT = f"{GCP_LOCATION}-aiplatform.googleapis.com"
PREDICT_URL = f"https://{API_ENDPOINT}/v1/projects/{GCP_PROJECT_ID}/locations/{GCP_LOCATION}/publishers/google/models/{MODEL_ID}:predictLongRunning"
FETCH_URL = f"https://{API_ENDPOINT}/v1/projects/{GCP_PROJECT_ID}/locations/{GCP_LOCATION}/publishers/google/models/{MODEL_ID}:fetchPredictOperation"
with open("gcp_creds.json", "w") as f: f.write(creds_json_str)
SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
credentials, _ = google.auth.load_credentials_from_file("gcp_creds.json", scopes=SCOPES)
def get_access_token():
auth_req = google.auth.transport.requests.Request()
credentials.refresh(auth_req)
return credentials.token
# --- 2. Core Video Generation Logic (Refactored for API) ---
# The function now returns a final JSON object instead of yielding updates.
def generate_video_api(prompt: str):
if not prompt:
return {"status": "error", "message": "Prompt cannot be empty."}
try:
headers = {"Authorization": f"Bearer {get_access_token()}", "Content-Type": "application/json"}
payload = {"instances": [{"prompt": prompt}], "parameters": {"aspectRatio": "16:9", "sampleCount": 1, "durationSeconds": 8, "personGeneration": "allow_all", "addWatermark": True, "includeRaiReason": True, "generateAudio": True}}
# Submit job
response = requests.post(PREDICT_URL, headers=headers, json=payload)
response.raise_for_status()
operation_name = response.json()["name"]
print(f"Successfully submitted job. Operation Name: {operation_name}")
# Poll for result
MAX_POLL_ATTEMPTS = 60
for i in range(MAX_POLL_ATTEMPTS):
print(f"Polling (Attempt {i+1}/{MAX_POLL_ATTEMPTS})...")
time.sleep(10) # Wait before polling
headers["Authorization"] = f"Bearer {get_access_token()}"
fetch_payload = {"operationName": operation_name}
poll_response = requests.post(FETCH_URL, headers=headers, json=fetch_payload)
poll_response.raise_for_status()
poll_result = poll_response.json()
if poll_result.get("done"):
print("Job finished.")
response_data = poll_result.get("response", {})
# Case 1: Success, video is present
if "videos" in response_data and response_data["videos"]:
video_base64 = response_data["videos"][0]["bytesBase64Encoded"]
return {"status": "success", "video_base64": video_base64}
# Case 2: Failure, an error message is present
error_message = "Video generation failed."
if "error" in poll_result:
error_details = poll_result["error"].get("message", "No details provided.")
error_message += f" API Error: {error_details}"
elif "raiResult" in response_data:
rai_reason = response_data.get("raiMediaFilteredReason", "Unknown reason.")
error_message += f" Content was blocked by safety filters ({rai_reason})."
else:
error_message += " The API did not return a video or a specific error."
return {"status": "error", "message": error_message}
return {"status": "error", "message": "Operation timed out."}
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e.response.text}")
return {"status": "error", "message": f"API Error: {e.response.status_code}. Details: {e.response.text}"}
except Exception as e:
print(f"An unexpected error occurred: {e}")
return {"status": "error", "message": f"An unexpected error occurred: {str(e)}"}
# --- 3. Gradio API Definition (No UI) ---
# We define the components that make up the API contract, but they are not made visible.
with gr.Blocks() as demo:
# Define the inputs and outputs for the API
prompt_input = gr.Textbox(label="prompt", visible=False)
output_json = gr.JSON(label="result", visible=False)
# Create the API endpoint named "predict"
# This will be available at /run/predict
gr.Interface(
fn=generate_video_api,
inputs=prompt_input,
outputs=output_json,
api_name="predict"
)
# The launch() call is still needed to start the web server that listens for API calls.
demo.launch() |