magic_hour 0.34.0__py3-none-any.whl → 0.36.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of magic_hour might be problematic. Click here for more details.
- magic_hour/README.md +35 -0
- magic_hour/core/base_client.py +6 -5
- magic_hour/core/query.py +12 -6
- magic_hour/core/request.py +3 -3
- magic_hour/core/response.py +18 -14
- magic_hour/core/utils.py +3 -3
- magic_hour/environment.py +1 -1
- magic_hour/helpers/__init__.py +3 -0
- magic_hour/helpers/download.py +75 -0
- magic_hour/resources/v1/README.md +33 -0
- magic_hour/resources/v1/ai_clothes_changer/README.md +73 -0
- magic_hour/resources/v1/ai_clothes_changer/client.py +146 -0
- magic_hour/resources/v1/ai_face_editor/README.md +110 -0
- magic_hour/resources/v1/ai_face_editor/client.py +168 -0
- magic_hour/resources/v1/ai_gif_generator/README.md +59 -0
- magic_hour/resources/v1/ai_gif_generator/client.py +119 -0
- magic_hour/resources/v1/ai_headshot_generator/README.md +60 -0
- magic_hour/resources/v1/ai_headshot_generator/client.py +140 -0
- magic_hour/resources/v1/ai_image_editor/README.md +64 -0
- magic_hour/resources/v1/ai_image_editor/client.py +136 -0
- magic_hour/resources/v1/ai_image_generator/README.md +66 -0
- magic_hour/resources/v1/ai_image_generator/client.py +139 -0
- magic_hour/resources/v1/ai_image_upscaler/README.md +67 -0
- magic_hour/resources/v1/ai_image_upscaler/client.py +150 -0
- magic_hour/resources/v1/ai_meme_generator/README.md +71 -0
- magic_hour/resources/v1/ai_meme_generator/client.py +127 -0
- magic_hour/resources/v1/ai_photo_editor/README.md +98 -7
- magic_hour/resources/v1/ai_photo_editor/client.py +174 -0
- magic_hour/resources/v1/ai_qr_code_generator/README.md +63 -0
- magic_hour/resources/v1/ai_qr_code_generator/client.py +123 -0
- magic_hour/resources/v1/ai_talking_photo/README.md +74 -0
- magic_hour/resources/v1/ai_talking_photo/client.py +170 -0
- magic_hour/resources/v1/animation/README.md +100 -0
- magic_hour/resources/v1/animation/client.py +218 -0
- magic_hour/resources/v1/auto_subtitle_generator/README.md +69 -0
- magic_hour/resources/v1/auto_subtitle_generator/client.py +178 -0
- magic_hour/resources/v1/face_detection/README.md +59 -0
- magic_hour/resources/v1/face_detection/__init__.py +10 -2
- magic_hour/resources/v1/face_detection/client.py +179 -0
- magic_hour/resources/v1/face_swap/README.md +105 -12
- magic_hour/resources/v1/face_swap/client.py +262 -28
- magic_hour/resources/v1/face_swap_photo/README.md +84 -0
- magic_hour/resources/v1/face_swap_photo/client.py +172 -0
- magic_hour/resources/v1/files/README.md +6 -0
- magic_hour/resources/v1/files/client.py +350 -0
- magic_hour/resources/v1/files/client_test.py +414 -0
- magic_hour/resources/v1/files/upload_urls/README.md +8 -0
- magic_hour/resources/v1/image_background_remover/README.md +68 -0
- magic_hour/resources/v1/image_background_remover/client.py +130 -0
- magic_hour/resources/v1/image_projects/README.md +8 -0
- magic_hour/resources/v1/image_projects/__init__.py +10 -2
- magic_hour/resources/v1/image_projects/client.py +138 -0
- magic_hour/resources/v1/image_projects/client_test.py +527 -0
- magic_hour/resources/v1/image_to_video/README.md +77 -9
- magic_hour/resources/v1/image_to_video/client.py +210 -8
- magic_hour/resources/v1/lip_sync/README.md +87 -13
- magic_hour/resources/v1/lip_sync/client.py +230 -28
- magic_hour/resources/v1/photo_colorizer/README.md +59 -0
- magic_hour/resources/v1/photo_colorizer/client.py +130 -0
- magic_hour/resources/v1/text_to_video/README.md +68 -0
- magic_hour/resources/v1/text_to_video/client.py +151 -0
- magic_hour/resources/v1/video_projects/README.md +8 -0
- magic_hour/resources/v1/video_projects/__init__.py +10 -2
- magic_hour/resources/v1/video_projects/client.py +137 -0
- magic_hour/resources/v1/video_projects/client_test.py +527 -0
- magic_hour/resources/v1/video_to_video/README.md +98 -14
- magic_hour/resources/v1/video_to_video/client.py +242 -28
- magic_hour/types/params/__init__.py +58 -0
- magic_hour/types/params/v1_ai_clothes_changer_generate_body_assets.py +33 -0
- magic_hour/types/params/v1_ai_face_editor_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_headshot_generator_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_image_editor_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_image_upscaler_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_photo_editor_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_talking_photo_generate_body_assets.py +26 -0
- magic_hour/types/params/v1_animation_generate_body_assets.py +39 -0
- magic_hour/types/params/v1_auto_subtitle_generator_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_face_detection_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_face_swap_create_body.py +24 -14
- magic_hour/types/params/v1_face_swap_create_body_style.py +33 -0
- magic_hour/types/params/v1_face_swap_generate_body_assets.py +56 -0
- magic_hour/types/params/v1_face_swap_generate_body_assets_face_mappings_item.py +25 -0
- magic_hour/types/params/v1_face_swap_photo_generate_body_assets.py +47 -0
- magic_hour/types/params/v1_face_swap_photo_generate_body_assets_face_mappings_item.py +25 -0
- magic_hour/types/params/v1_image_background_remover_generate_body_assets.py +27 -0
- magic_hour/types/params/v1_image_to_video_create_body.py +14 -6
- magic_hour/types/params/v1_image_to_video_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_lip_sync_create_body.py +12 -14
- magic_hour/types/params/v1_lip_sync_generate_body_assets.py +36 -0
- magic_hour/types/params/v1_photo_colorizer_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_video_to_video_create_body.py +12 -14
- magic_hour/types/params/v1_video_to_video_generate_body_assets.py +27 -0
- magic_hour-0.36.0.dist-info/METADATA +303 -0
- {magic_hour-0.34.0.dist-info → magic_hour-0.36.0.dist-info}/RECORD +96 -68
- magic_hour-0.34.0.dist-info/METADATA +0 -166
- {magic_hour-0.34.0.dist-info → magic_hour-0.36.0.dist-info}/LICENSE +0 -0
- {magic_hour-0.34.0.dist-info → magic_hour-0.36.0.dist-info}/WHEEL +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import typing
|
|
2
3
|
import typing_extensions
|
|
3
4
|
|
|
@@ -9,13 +10,92 @@ from magic_hour.core import (
|
|
|
9
10
|
to_encodable,
|
|
10
11
|
type_utils,
|
|
11
12
|
)
|
|
13
|
+
from magic_hour.resources.v1.video_projects.client import (
|
|
14
|
+
AsyncVideoProjectsClient,
|
|
15
|
+
VideoProjectsClient,
|
|
16
|
+
)
|
|
12
17
|
from magic_hour.types import models, params
|
|
13
18
|
|
|
14
19
|
|
|
20
|
+
logging.basicConfig(level=logging.INFO)
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
15
24
|
class TextToVideoClient:
|
|
16
25
|
def __init__(self, *, base_client: SyncBaseClient):
|
|
17
26
|
self._base_client = base_client
|
|
18
27
|
|
|
28
|
+
def generate(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
end_seconds: float,
|
|
32
|
+
orientation: typing_extensions.Literal["landscape", "portrait", "square"],
|
|
33
|
+
style: params.V1TextToVideoCreateBodyStyle,
|
|
34
|
+
name: typing.Union[
|
|
35
|
+
typing.Optional[str], type_utils.NotGiven
|
|
36
|
+
] = type_utils.NOT_GIVEN,
|
|
37
|
+
resolution: typing.Union[
|
|
38
|
+
typing.Optional[typing_extensions.Literal["1080p", "480p", "720p"]],
|
|
39
|
+
type_utils.NotGiven,
|
|
40
|
+
] = type_utils.NOT_GIVEN,
|
|
41
|
+
wait_for_completion: bool = True,
|
|
42
|
+
download_outputs: bool = True,
|
|
43
|
+
download_directory: typing.Optional[str] = None,
|
|
44
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Generate text-to-video (alias for create with additional functionality).
|
|
48
|
+
|
|
49
|
+
Create a Text To Video video. The estimated frame cost is calculated using 30 FPS. This amount is deducted from your account balance when a video is queued. Once the video is complete, the cost will be updated based on the actual number of frames rendered.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
name: The name of video. This value is mainly used for your own identification of the video.
|
|
53
|
+
resolution: Controls the output video resolution. Defaults to `720p` if not specified.
|
|
54
|
+
end_seconds: The total duration of the output video in seconds.
|
|
55
|
+
orientation: Determines the orientation of the output video
|
|
56
|
+
style: V1TextToVideoCreateBodyStyle
|
|
57
|
+
wait_for_completion: Whether to wait for the video project to complete
|
|
58
|
+
download_outputs: Whether to download the outputs
|
|
59
|
+
download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
|
|
60
|
+
request_options: Additional options to customize the HTTP request
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
V1VideoProjectsGetResponseWithDownloads: The response from the Text-to-Video API with the downloaded paths if `download_outputs` is True.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
```py
|
|
67
|
+
response = client.v1.text_to_video.generate(
|
|
68
|
+
end_seconds=5.0,
|
|
69
|
+
orientation="landscape",
|
|
70
|
+
style={"prompt": "a dog running through a meadow"},
|
|
71
|
+
resolution="720p",
|
|
72
|
+
wait_for_completion=True,
|
|
73
|
+
download_outputs=True,
|
|
74
|
+
download_directory="outputs/",
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
create_response = self.create(
|
|
80
|
+
end_seconds=end_seconds,
|
|
81
|
+
orientation=orientation,
|
|
82
|
+
style=style,
|
|
83
|
+
name=name,
|
|
84
|
+
resolution=resolution,
|
|
85
|
+
request_options=request_options,
|
|
86
|
+
)
|
|
87
|
+
logger.info(f"Text-to-Video response: {create_response}")
|
|
88
|
+
|
|
89
|
+
video_projects_client = VideoProjectsClient(base_client=self._base_client)
|
|
90
|
+
response = video_projects_client.check_result(
|
|
91
|
+
id=create_response.id,
|
|
92
|
+
wait_for_completion=wait_for_completion,
|
|
93
|
+
download_outputs=download_outputs,
|
|
94
|
+
download_directory=download_directory,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return response
|
|
98
|
+
|
|
19
99
|
def create(
|
|
20
100
|
self,
|
|
21
101
|
*,
|
|
@@ -102,6 +182,77 @@ class AsyncTextToVideoClient:
|
|
|
102
182
|
def __init__(self, *, base_client: AsyncBaseClient):
|
|
103
183
|
self._base_client = base_client
|
|
104
184
|
|
|
185
|
+
async def generate(
|
|
186
|
+
self,
|
|
187
|
+
*,
|
|
188
|
+
end_seconds: float,
|
|
189
|
+
orientation: typing_extensions.Literal["landscape", "portrait", "square"],
|
|
190
|
+
style: params.V1TextToVideoCreateBodyStyle,
|
|
191
|
+
name: typing.Union[
|
|
192
|
+
typing.Optional[str], type_utils.NotGiven
|
|
193
|
+
] = type_utils.NOT_GIVEN,
|
|
194
|
+
resolution: typing.Union[
|
|
195
|
+
typing.Optional[typing_extensions.Literal["1080p", "480p", "720p"]],
|
|
196
|
+
type_utils.NotGiven,
|
|
197
|
+
] = type_utils.NOT_GIVEN,
|
|
198
|
+
wait_for_completion: bool = True,
|
|
199
|
+
download_outputs: bool = True,
|
|
200
|
+
download_directory: typing.Optional[str] = None,
|
|
201
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
202
|
+
):
|
|
203
|
+
"""
|
|
204
|
+
Generate text-to-video (alias for create with additional functionality).
|
|
205
|
+
|
|
206
|
+
Create a Text To Video video. The estimated frame cost is calculated using 30 FPS. This amount is deducted from your account balance when a video is queued. Once the video is complete, the cost will be updated based on the actual number of frames rendered.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
name: The name of video. This value is mainly used for your own identification of the video.
|
|
210
|
+
resolution: Controls the output video resolution. Defaults to `720p` if not specified.
|
|
211
|
+
end_seconds: The total duration of the output video in seconds.
|
|
212
|
+
orientation: Determines the orientation of the output video
|
|
213
|
+
style: V1TextToVideoCreateBodyStyle
|
|
214
|
+
wait_for_completion: Whether to wait for the video project to complete
|
|
215
|
+
download_outputs: Whether to download the outputs
|
|
216
|
+
download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
|
|
217
|
+
request_options: Additional options to customize the HTTP request
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
V1VideoProjectsGetResponseWithDownloads: The response from the Text-to-Video API with the downloaded paths if `download_outputs` is True.
|
|
221
|
+
|
|
222
|
+
Examples:
|
|
223
|
+
```py
|
|
224
|
+
response = await client.v1.text_to_video.generate(
|
|
225
|
+
end_seconds=5.0,
|
|
226
|
+
orientation="landscape",
|
|
227
|
+
style={"prompt": "a dog running through a meadow"},
|
|
228
|
+
resolution="720p",
|
|
229
|
+
wait_for_completion=True,
|
|
230
|
+
download_outputs=True,
|
|
231
|
+
download_directory="outputs/",
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
create_response = await self.create(
|
|
237
|
+
end_seconds=end_seconds,
|
|
238
|
+
orientation=orientation,
|
|
239
|
+
style=style,
|
|
240
|
+
name=name,
|
|
241
|
+
resolution=resolution,
|
|
242
|
+
request_options=request_options,
|
|
243
|
+
)
|
|
244
|
+
logger.info(f"Text-to-Video response: {create_response}")
|
|
245
|
+
|
|
246
|
+
video_projects_client = AsyncVideoProjectsClient(base_client=self._base_client)
|
|
247
|
+
response = await video_projects_client.check_result(
|
|
248
|
+
id=create_response.id,
|
|
249
|
+
wait_for_completion=wait_for_completion,
|
|
250
|
+
download_outputs=download_outputs,
|
|
251
|
+
download_directory=download_directory,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return response
|
|
255
|
+
|
|
105
256
|
async def create(
|
|
106
257
|
self,
|
|
107
258
|
*,
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# v1_video_projects
|
|
2
|
+
|
|
3
|
+
## Module Functions
|
|
4
|
+
|
|
5
|
+
<!-- CUSTOM DOCS START -->
|
|
6
|
+
|
|
7
|
+
<!-- CUSTOM DOCS END -->
|
|
1
8
|
|
|
2
9
|
### Delete video <a name="delete"></a>
|
|
3
10
|
|
|
@@ -83,3 +90,4 @@ res = await client.v1.video_projects.get(id="cuid-example")
|
|
|
83
90
|
|
|
84
91
|
##### Example
|
|
85
92
|
`{"created_at": "1970-01-01T00:00:00", "credits_charged": 450, "download": {"expires_at": "2024-10-19T05:16:19.027Z", "url": "https://videos.magichour.ai/id/output.mp4"}, "downloads": [{"expires_at": "2024-10-19T05:16:19.027Z", "url": "https://videos.magichour.ai/id/output.mp4"}], "enabled": True, "end_seconds": 15.0, "error": {"code": "no_source_face", "message": "Please use an image with a detectable face"}, "fps": 30.0, "height": 960, "id": "cuid-example", "name": "Example Name", "start_seconds": 0.0, "status": "complete", "total_frame_cost": 450, "type_": "FACE_SWAP", "width": 512}`
|
|
93
|
+
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
from .client import
|
|
1
|
+
from .client import (
|
|
2
|
+
AsyncVideoProjectsClient,
|
|
3
|
+
V1VideoProjectsGetResponseWithDownloads,
|
|
4
|
+
VideoProjectsClient,
|
|
5
|
+
)
|
|
2
6
|
|
|
3
7
|
|
|
4
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AsyncVideoProjectsClient",
|
|
10
|
+
"V1VideoProjectsGetResponseWithDownloads",
|
|
11
|
+
"VideoProjectsClient",
|
|
12
|
+
]
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import pydantic
|
|
4
|
+
import time
|
|
1
5
|
import typing
|
|
2
6
|
|
|
3
7
|
from magic_hour.core import (
|
|
@@ -6,13 +10,87 @@ from magic_hour.core import (
|
|
|
6
10
|
SyncBaseClient,
|
|
7
11
|
default_request_options,
|
|
8
12
|
)
|
|
13
|
+
from magic_hour.helpers.download import download_files_async, download_files_sync
|
|
9
14
|
from magic_hour.types import models
|
|
10
15
|
|
|
11
16
|
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class V1VideoProjectsGetResponseWithDownloads(models.V1VideoProjectsGetResponse):
|
|
21
|
+
downloaded_paths: typing.Optional[typing.List[str]] = pydantic.Field(
|
|
22
|
+
default=None, alias="downloaded_paths"
|
|
23
|
+
)
|
|
24
|
+
"""
|
|
25
|
+
The paths to the downloaded files.
|
|
26
|
+
|
|
27
|
+
This field is only populated if `download_outputs` is True and the video project is complete.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
12
31
|
class VideoProjectsClient:
|
|
13
32
|
def __init__(self, *, base_client: SyncBaseClient):
|
|
14
33
|
self._base_client = base_client
|
|
15
34
|
|
|
35
|
+
def check_result(
|
|
36
|
+
self,
|
|
37
|
+
id: str,
|
|
38
|
+
wait_for_completion: bool,
|
|
39
|
+
download_outputs: bool,
|
|
40
|
+
download_directory: typing.Optional[str] = None,
|
|
41
|
+
) -> V1VideoProjectsGetResponseWithDownloads:
|
|
42
|
+
"""
|
|
43
|
+
Check the result of a video project with optional waiting and downloading.
|
|
44
|
+
|
|
45
|
+
This method retrieves the status of a video project and optionally waits for completion
|
|
46
|
+
and downloads the output files.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
id: Unique ID of the video project
|
|
50
|
+
wait_for_completion: Whether to wait for the video project to complete
|
|
51
|
+
download_outputs: Whether to download the outputs
|
|
52
|
+
download_directory: The directory to download the outputs to. If not provided,
|
|
53
|
+
the outputs will be downloaded to the current working directory
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
V1VideoProjectsGetResponseWithDownloads: The video project response with optional
|
|
57
|
+
downloaded file paths included
|
|
58
|
+
"""
|
|
59
|
+
api_response = self.get(id=id)
|
|
60
|
+
if not wait_for_completion:
|
|
61
|
+
response = V1VideoProjectsGetResponseWithDownloads(
|
|
62
|
+
**api_response.model_dump()
|
|
63
|
+
)
|
|
64
|
+
return response
|
|
65
|
+
|
|
66
|
+
poll_interval = float(os.getenv("MAGIC_HOUR_POLL_INTERVAL", "0.5"))
|
|
67
|
+
|
|
68
|
+
status = api_response.status
|
|
69
|
+
|
|
70
|
+
while status not in ["complete", "error", "canceled"]:
|
|
71
|
+
api_response = self.get(id=id)
|
|
72
|
+
status = api_response.status
|
|
73
|
+
time.sleep(poll_interval)
|
|
74
|
+
|
|
75
|
+
if api_response.status != "complete":
|
|
76
|
+
log = logger.error if api_response.status == "error" else logger.info
|
|
77
|
+
log(
|
|
78
|
+
f"Video project {id} has status {api_response.status}: {api_response.error}"
|
|
79
|
+
)
|
|
80
|
+
return V1VideoProjectsGetResponseWithDownloads(**api_response.model_dump())
|
|
81
|
+
|
|
82
|
+
if not download_outputs:
|
|
83
|
+
return V1VideoProjectsGetResponseWithDownloads(**api_response.model_dump())
|
|
84
|
+
|
|
85
|
+
downloaded_paths = download_files_sync(
|
|
86
|
+
downloads=api_response.downloads,
|
|
87
|
+
download_directory=download_directory,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return V1VideoProjectsGetResponseWithDownloads(
|
|
91
|
+
**api_response.model_dump(), downloaded_paths=downloaded_paths
|
|
92
|
+
)
|
|
93
|
+
|
|
16
94
|
def delete(
|
|
17
95
|
self, *, id: str, request_options: typing.Optional[RequestOptions] = None
|
|
18
96
|
) -> None:
|
|
@@ -95,6 +173,65 @@ class AsyncVideoProjectsClient:
|
|
|
95
173
|
def __init__(self, *, base_client: AsyncBaseClient):
|
|
96
174
|
self._base_client = base_client
|
|
97
175
|
|
|
176
|
+
async def check_result(
|
|
177
|
+
self,
|
|
178
|
+
id: str,
|
|
179
|
+
wait_for_completion: bool,
|
|
180
|
+
download_outputs: bool,
|
|
181
|
+
download_directory: typing.Optional[str] = None,
|
|
182
|
+
) -> V1VideoProjectsGetResponseWithDownloads:
|
|
183
|
+
"""
|
|
184
|
+
Check the result of a video project with optional waiting and downloading.
|
|
185
|
+
|
|
186
|
+
This method retrieves the status of a video project and optionally waits for completion
|
|
187
|
+
and downloads the output files.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
id: Unique ID of the video project
|
|
191
|
+
wait_for_completion: Whether to wait for the video project to complete
|
|
192
|
+
download_outputs: Whether to download the outputs
|
|
193
|
+
download_directory: The directory to download the outputs to. If not provided,
|
|
194
|
+
the outputs will be downloaded to the current working directory
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
V1VideoProjectsGetResponseWithDownloads: The video project response with optional
|
|
198
|
+
downloaded file paths included
|
|
199
|
+
"""
|
|
200
|
+
api_response = await self.get(id=id)
|
|
201
|
+
if not wait_for_completion:
|
|
202
|
+
response = V1VideoProjectsGetResponseWithDownloads(
|
|
203
|
+
**api_response.model_dump()
|
|
204
|
+
)
|
|
205
|
+
return response
|
|
206
|
+
|
|
207
|
+
poll_interval = float(os.getenv("MAGIC_HOUR_POLL_INTERVAL", "0.5"))
|
|
208
|
+
|
|
209
|
+
status = api_response.status
|
|
210
|
+
|
|
211
|
+
while status not in ["complete", "error", "canceled"]:
|
|
212
|
+
api_response = await self.get(id=id)
|
|
213
|
+
status = api_response.status
|
|
214
|
+
time.sleep(poll_interval)
|
|
215
|
+
|
|
216
|
+
if api_response.status != "complete":
|
|
217
|
+
log = logger.error if api_response.status == "error" else logger.info
|
|
218
|
+
log(
|
|
219
|
+
f"Video project {id} has status {api_response.status}: {api_response.error}"
|
|
220
|
+
)
|
|
221
|
+
return V1VideoProjectsGetResponseWithDownloads(**api_response.model_dump())
|
|
222
|
+
|
|
223
|
+
if not download_outputs:
|
|
224
|
+
return V1VideoProjectsGetResponseWithDownloads(**api_response.model_dump())
|
|
225
|
+
|
|
226
|
+
downloaded_paths = await download_files_async(
|
|
227
|
+
downloads=api_response.downloads,
|
|
228
|
+
download_directory=download_directory,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return V1VideoProjectsGetResponseWithDownloads(
|
|
232
|
+
**api_response.model_dump(), downloaded_paths=downloaded_paths
|
|
233
|
+
)
|
|
234
|
+
|
|
98
235
|
async def delete(
|
|
99
236
|
self, *, id: str, request_options: typing.Optional[RequestOptions] = None
|
|
100
237
|
) -> None:
|