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()