Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 100 additions & 3 deletions edenai_apis/apis/google/google_video_api.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import base64
import json
import mimetypes
from datetime import datetime, timezone
from io import BytesIO
from pathlib import Path
from time import sleep, time
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional

import requests
from dateutil.parser import parse
from google.cloud import videointelligence

from edenai_apis.apis.amazon.helpers import check_webhook_result
from edenai_apis.apis.google.google_helpers import (
GoogleVideoFeatures,
calculate_usage_tokens,
google_video_get_job,
score_to_content,
Expand Down Expand Up @@ -69,6 +69,9 @@
VideoTextBoundingBox,
VideoTextFrames,
)
from edenai_apis.features.video.generation_async.generation_async_dataclass import (
GenerationAsyncDataClass,
)
from edenai_apis.features.video.video_interface import VideoInterface
from edenai_apis.utils.exception import (
ProviderException,
Expand All @@ -82,6 +85,10 @@
AsyncResponseType,
ResponseType,
)
from edenai_apis.utils.upload_s3 import (
USER_PROCESS,
upload_file_bytes_to_s3,
)


class GoogleVideoApi(VideoInterface):
Expand Down Expand Up @@ -918,3 +925,93 @@ def video__question_answer_async__get_job_result(
standardized_response=standardized_response,
provider_job_id=provider_job_id,
)

def video__generation_async__launch_job(
self,
text: str,
duration: Optional[int] = 6,
fps: Optional[int] = 24,
dimension: Optional[str] = "1280x720",
seed: Optional[float] = 12,
file: Optional[str] = None,
file_url: Optional[str] = None,
model: Optional[str] = None,
**kwargs,
) -> AsyncLaunchJobResponseType:
Comment on lines +929 to +940
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove or implement unused parameters.

The following parameters are accepted but not used in the implementation:

  • dimension (line 935)
  • fps (line 934)
  • seed (line 936)
  • file_url (line 938)

Either implement these parameters in the request payload or remove them from the method signature to avoid confusion.

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 929-929: Too many arguments (9/7)

(R0913)


[refactor] 929-929: Too many positional arguments (9/5)

(R0917)

🤖 Prompt for AI Agents
In edenai_apis/apis/google/google_video_api.py around lines 929 to 940, the
parameters dimension, fps, seed, and file_url are declared but not used in the
method video__generation_async__launch_job. To fix this, either remove these
unused parameters from the method signature if they are unnecessary, or update
the method implementation to include them appropriately in the request payload
or processing logic to ensure they have an effect.

api_key = self.api_settings.get("genai_api_key")
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:predictLongRunning?key={api_key}"
prompt = {"prompt": text}
if file:
with open(file, "rb") as file_:
file_content = file_.read()
input_image_base64 = base64.b64encode(file_content).decode("utf-8")
mime_type = mimetypes.guess_type(file)[0]
image = {
"bytesBase64Encoded": input_image_base64,
"mimeType": mime_type,
}
prompt["image"] = image

payload = {
"instances": [prompt],
"parameters": {
"durationSeconds": duration,
# "personGeneration": "allow_adult",
},
}
response = requests.post(url=url, json=payload)
try:
original_response = response.json()
except json.JSONDecodeError as exc:
raise ProviderException(
"An error occurred while parsing the response."
) from exc

if response.status_code != 200:
raise ProviderException(
message=original_response["error"]["message"],
code=response.status_code,
)
# {'name': 'models/veo-2.0-generate-001/operations/0gbm5qefte2y'}

provider_job_id = original_response["name"]
return AsyncLaunchJobResponseType(provider_job_id=provider_job_id)

def video__generation_async__get_job_result(
self, provider_job_id: str
) -> GenerationAsyncDataClass:
api_key = self.api_settings.get("genai_api_key")
url = f"https://generativelanguage.googleapis.com/v1beta/{provider_job_id}?key={api_key}"
response = requests.get(url)
try:
original_response = response.json()
except json.JSONDecodeError as exc:
raise ProviderException(
"An error occurred while parsing the response."
) from exc
if "error" in original_response:
raise ProviderException(
message=original_response["error"]["message"],
code=original_response["error"]["code"],
)
if original_response.get("done") is True:
uri = original_response["response"]["generateVideoResponse"][
"generatedSamples"
][0]["video"]["uri"]
headers = {"x-goog-api-key": api_key}
response = requests.get(uri, headers=headers)
file_content = response.content
base64_encoded_string = base64.b64encode(file_content).decode("utf-8")
resource_url = upload_file_bytes_to_s3(
BytesIO(file_content), ".mp4", USER_PROCESS
)
Comment on lines +1002 to +1007
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for S3 upload and consider memory usage for large videos.

  1. The entire video content is loaded into memory which could cause issues with large files.
  2. No error handling if S3 upload fails - the video data would be lost.

Consider streaming the video content or implementing error handling:

 response = requests.get(uri, headers=headers)
+if response.status_code != 200:
+    raise ProviderException(
+        message=f"Failed to download video: {response.status_code}",
+        code=response.status_code
+    )
 file_content = response.content
 base64_encoded_string = base64.b64encode(file_content).decode("utf-8")
-resource_url = upload_file_bytes_to_s3(
-    BytesIO(file_content), ".mp4", USER_PROCESS
-)
+try:
+    resource_url = upload_file_bytes_to_s3(
+        BytesIO(file_content), ".mp4", USER_PROCESS
+    )
+except Exception as exc:
+    raise ProviderException(
+        message=f"Failed to upload video to S3: {str(exc)}",
+        code=500
+    ) from exc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response = requests.get(uri, headers=headers)
file_content = response.content
base64_encoded_string = base64.b64encode(file_content).decode("utf-8")
resource_url = upload_file_bytes_to_s3(
BytesIO(file_content), ".mp4", USER_PROCESS
)
response = requests.get(uri, headers=headers)
if response.status_code != 200:
raise ProviderException(
message=f"Failed to download video: {response.status_code}",
code=response.status_code
)
file_content = response.content
base64_encoded_string = base64.b64encode(file_content).decode("utf-8")
try:
resource_url = upload_file_bytes_to_s3(
BytesIO(file_content), ".mp4", USER_PROCESS
)
except Exception as exc:
raise ProviderException(
message=f"Failed to upload video to S3: {str(exc)}",
code=500
) from exc
🤖 Prompt for AI Agents
In edenai_apis/apis/google/google_video_api.py around lines 1002 to 1007, the
code loads the entire video content into memory and uploads it to S3 without
error handling. To fix this, implement error handling around the S3 upload call
to catch and manage upload failures gracefully. Additionally, refactor the code
to stream the video content instead of loading it fully into memory, for example
by using a streaming request or chunked upload, to reduce memory usage with
large video files.

standardized_response = GenerationAsyncDataClass(
video=base64_encoded_string, video_resource_url=resource_url
)
return AsyncResponseType[GenerationAsyncDataClass](
original_response=original_response,
standardized_response=standardized_response,
provider_job_id=provider_job_id,
)

return AsyncPendingResponseType(provider_job_id=provider_job_id)
3 changes: 3 additions & 0 deletions edenai_apis/apis/google/info.json
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,9 @@
]
},
"version": "v1Beta"
},
"generation_async": {
"version": "v1Beta"
}
},
"image": {
Expand Down
31 changes: 31 additions & 0 deletions edenai_apis/apis/google/outputs/video/generation_async_output.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions edenai_apis/apis/microsoft/info.json
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,11 @@
"version": "Azure AI Foundry"
}
},
"video": {
"generation_async": {
"version": "Azure AI Foundry"
}
},
"_metadata": {
"privacy_url": "https://privacy.microsoft.com/en-us"
}
Expand Down
2 changes: 2 additions & 0 deletions edenai_apis/apis/microsoft/microsoft_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
get_microsoft_urls,
)
from edenai_apis.apis.microsoft.microsoft_image_api import MicrosoftImageApi
from edenai_apis.apis.microsoft.microsoft_video_api import MicrosoftVideoApi
from edenai_apis.apis.microsoft.microsoft_ocr_api import MicrosoftOcrApi
from edenai_apis.apis.microsoft.microsoft_text_api import MicrosoftTextApi
from edenai_apis.apis.microsoft.microsoft_translation_api import MicrosoftTranslationApi
Expand All @@ -26,6 +27,7 @@ class MicrosoftApi(
MicrosoftAudioApi,
MicrosoftMultimodalApi,
MicrosoftLLMApi,
MicrosoftVideoApi,
):
provider_name = "microsoft"

Expand Down
113 changes: 113 additions & 0 deletions edenai_apis/apis/microsoft/microsoft_video_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import base64
import json
from io import BytesIO
from typing import Optional

import requests

from edenai_apis.features.video.generation_async.generation_async_dataclass import (
GenerationAsyncDataClass,
)
from edenai_apis.features.video.video_interface import VideoInterface
from edenai_apis.utils.exception import (
ProviderException,
)
from edenai_apis.utils.types import (
AsyncLaunchJobResponseType,
AsyncPendingResponseType,
AsyncResponseType,
)
from edenai_apis.utils.upload_s3 import (
USER_PROCESS,
upload_file_bytes_to_s3,
)


class MicrosoftVideoApi(VideoInterface):
def video__generation_async__launch_job(
self,
text: str,
duration: Optional[int] = 6,
fps: Optional[int] = 24,
dimension: Optional[str] = "1280x720",
seed: Optional[float] = 12,
file: Optional[str] = None,
file_url: Optional[str] = None,
model: Optional[str] = None,
**kwargs,
Comment on lines +27 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused parameters to match implementation.

The following parameters are not used in the implementation:

  • fps (line 31)
  • seed (line 33)
  • file (line 34)
  • file_url (line 35)

Remove these parameters or implement them in the request payload.

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 27-27: Too many arguments (9/7)

(R0913)


[refactor] 27-27: Too many positional arguments (9/5)

(R0917)

🤖 Prompt for AI Agents
In edenai_apis/apis/microsoft/microsoft_video_api.py between lines 27 and 37,
the parameters fps, seed, file, and file_url are declared but not used in the
function implementation. To fix this, either remove these unused parameters from
the function signature to keep it clean and consistent, or if they are intended
to be part of the video generation request, update the function to include them
properly in the request payload sent to the API.

) -> AsyncLaunchJobResponseType:
base_url = self.azure_ai_credentials.get("azure_api_sora")
api_key = self.azure_ai_credentials.get("azure_api_key")
url = f"{base_url}openai/v1/video/generations/jobs?api-version=preview"
try:
height, width = dimension.split("x")
except (ValueError, AttributeError) as exc:
raise ProviderException(
message=f"Invalid dimension format: {dimension}. Expected format: 'heightxwidth' (e.g., '1280x720')",
code=400,
) from exc
payload = {
"model": model,
"prompt": text,
"height": height,
"width": width,
"n_seconds": duration,
}
response = requests.post(
url=url, json=payload, headers={"Authorization": api_key}
)
try:
original_response = response.json()
except json.JSONDecodeError as exc:
raise ProviderException(
"An error occurred while parsing the response."
) from exc
if response.status_code > 201:
raise ProviderException(
message=original_response["error"]["message"],
code=response.status_code,
)

provider_job_id = original_response["id"]
return AsyncLaunchJobResponseType(provider_job_id=provider_job_id)

def video__generation_async__get_job_result(
self, provider_job_id: str
) -> GenerationAsyncDataClass:
base_url = self.azure_ai_credentials.get("azure_api_sora")
api_key = self.azure_ai_credentials.get("azure_api_key")
url = f"{base_url}openai/v1/video/generations/jobs/{provider_job_id}?api-version=preview"

response = requests.get(url, headers={"Authorization": api_key})
try:
original_response = response.json()
except json.JSONDecodeError as exc:
raise ProviderException(
"An error occurred while parsing the response."
) from exc
if "error" in original_response:
raise ProviderException(
message=original_response["error"]["message"],
code=original_response["error"]["code"],
)
if original_response.get("status") == "succeeded":
generations = original_response.get("generations", [])
generation_id = generations[0].get("id")
video_url = f"{base_url}openai/v1/video/generations/{generation_id}/content/video?api-version=preview"
video_response = requests.get(video_url, headers={"Authorization": api_key})
base64_encoded_string = base64.b64encode(video_response.content).decode(
"utf-8"
)
resource_url = upload_file_bytes_to_s3(
BytesIO(video_response.content), ".mp4", USER_PROCESS
)
Comment on lines +97 to +103
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for video download and S3 upload.

The code lacks error handling for:

  1. Failed video download (no status check for video_response)
  2. S3 upload failures

Apply this fix:

 video_response = requests.get(video_url, headers={"Authorization": api_key})
+if video_response.status_code != 200:
+    raise ProviderException(
+        message=f"Failed to download video: {video_response.status_code}",
+        code=video_response.status_code
+    )
 base64_encoded_string = base64.b64encode(video_response.content).decode(
     "utf-8"
 )
-resource_url = upload_file_bytes_to_s3(
-    BytesIO(video_response.content), ".mp4", USER_PROCESS
-)
+try:
+    resource_url = upload_file_bytes_to_s3(
+        BytesIO(video_response.content), ".mp4", USER_PROCESS
+    )
+except Exception as exc:
+    raise ProviderException(
+        message=f"Failed to upload video to S3: {str(exc)}",
+        code=500
+    ) from exc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
video_response = requests.get(video_url, headers={"Authorization": api_key})
base64_encoded_string = base64.b64encode(video_response.content).decode(
"utf-8"
)
resource_url = upload_file_bytes_to_s3(
BytesIO(video_response.content), ".mp4", USER_PROCESS
)
video_response = requests.get(video_url, headers={"Authorization": api_key})
if video_response.status_code != 200:
raise ProviderException(
message=f"Failed to download video: {video_response.status_code}",
code=video_response.status_code
)
base64_encoded_string = base64.b64encode(video_response.content).decode(
"utf-8"
)
try:
resource_url = upload_file_bytes_to_s3(
BytesIO(video_response.content), ".mp4", USER_PROCESS
)
except Exception as exc:
raise ProviderException(
message=f"Failed to upload video to S3: {str(exc)}",
code=500
) from exc
🤖 Prompt for AI Agents
In edenai_apis/apis/microsoft/microsoft_video_api.py around lines 91 to 97, add
error handling for the video download and S3 upload steps. Check the status code
of video_response after the requests.get call and raise or handle an error if
the download failed. Also, wrap the upload_file_bytes_to_s3 call in a try-except
block to catch and handle any exceptions during the S3 upload, ensuring the
function handles failures gracefully.

standardized_response = GenerationAsyncDataClass(
video=base64_encoded_string, video_resource_url=resource_url
)
return AsyncResponseType[GenerationAsyncDataClass](
original_response=original_response,
standardized_response=standardized_response,
provider_job_id=provider_job_id,
)

return AsyncPendingResponseType(provider_job_id=provider_job_id)

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion edenai_apis/apis/minimax/minimax_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,15 @@ def video__generation_async__get_job_result(
raise ProviderException(
message=base_msg["status_msg"], code=base_msg["status_code"]
)
video_url = file_response_data["file"]["download_url"]
video_response = requests.get(video_url)
base64_encoded_string = base64.b64encode(video_response.content).decode(
"utf-8"
)
return AsyncResponseType(
original_response=original_response,
standardized_response=GenerationAsyncDataClass(
video="",
video=base64_encoded_string,
video_resource_url=file_response_data["file"]["download_url"],
),
provider_job_id=provider_job_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def generation_async_arguments(provider_name: str) -> Dict:
)
file_wrapper = FileWrapper(image_path, "", file_info)
return {
"text": "Make this image move like the ocean",
"text": "Panning wide shot of a calico kitten sleeping in the sunshine",
"duration": 6,
"fps": 24,
"seed": 12,
Expand All @@ -34,5 +34,7 @@ def generation_async_arguments(provider_name: str) -> Dict:
"amazon": "amazon.nova-reel-v1:0",
"minimax": "MiniMax-Hailuo-02",
"bytedance": "seedance-1-0-lite-t2v-250428",
"google": "veo-2.0-generate-001",
"microsoft": "sora",
},
}
Loading