magic_hour 0.40.0__py3-none-any.whl → 0.44.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.
Files changed (73) hide show
  1. magic_hour/README.md +2 -3
  2. magic_hour/environment.py +1 -1
  3. magic_hour/helpers/download.py +2 -0
  4. magic_hour/resources/v1/README.md +2 -3
  5. magic_hour/resources/v1/ai_clothes_changer/README.md +13 -14
  6. magic_hour/resources/v1/ai_face_editor/README.md +26 -27
  7. magic_hour/resources/v1/ai_gif_generator/README.md +12 -13
  8. magic_hour/resources/v1/ai_gif_generator/client.py +2 -2
  9. magic_hour/resources/v1/ai_headshot_generator/README.md +13 -14
  10. magic_hour/resources/v1/ai_headshot_generator/client.py +2 -2
  11. magic_hour/resources/v1/ai_image_editor/README.md +24 -17
  12. magic_hour/resources/v1/ai_image_editor/client.py +40 -10
  13. magic_hour/resources/v1/ai_image_generator/README.md +26 -18
  14. magic_hour/resources/v1/ai_image_generator/client.py +14 -6
  15. magic_hour/resources/v1/ai_image_upscaler/README.md +14 -15
  16. magic_hour/resources/v1/ai_meme_generator/README.md +12 -13
  17. magic_hour/resources/v1/ai_photo_editor/README.md +22 -23
  18. magic_hour/resources/v1/ai_qr_code_generator/README.md +13 -14
  19. magic_hour/resources/v1/ai_qr_code_generator/client.py +4 -4
  20. magic_hour/resources/v1/ai_talking_photo/README.md +16 -17
  21. magic_hour/resources/v1/ai_voice_cloner/README.md +62 -0
  22. magic_hour/resources/v1/ai_voice_cloner/__init__.py +4 -0
  23. magic_hour/resources/v1/ai_voice_cloner/client.py +272 -0
  24. magic_hour/resources/v1/ai_voice_generator/README.md +66 -10
  25. magic_hour/resources/v1/ai_voice_generator/client.py +122 -0
  26. magic_hour/resources/v1/animation/README.md +24 -25
  27. magic_hour/resources/v1/audio_projects/README.md +58 -13
  28. magic_hour/resources/v1/audio_projects/__init__.py +10 -2
  29. magic_hour/resources/v1/audio_projects/client.py +137 -0
  30. magic_hour/resources/v1/audio_projects/client_test.py +520 -0
  31. magic_hour/resources/v1/auto_subtitle_generator/README.md +15 -16
  32. magic_hour/resources/v1/client.py +6 -0
  33. magic_hour/resources/v1/face_detection/README.md +21 -20
  34. magic_hour/resources/v1/face_swap/README.md +23 -25
  35. magic_hour/resources/v1/face_swap/client.py +2 -2
  36. magic_hour/resources/v1/face_swap_photo/README.md +13 -14
  37. magic_hour/resources/v1/files/README.md +1 -5
  38. magic_hour/resources/v1/files/upload_urls/README.md +11 -10
  39. magic_hour/resources/v1/files/upload_urls/client.py +6 -4
  40. magic_hour/resources/v1/image_background_remover/README.md +11 -12
  41. magic_hour/resources/v1/image_projects/README.md +12 -16
  42. magic_hour/resources/v1/image_to_video/README.md +19 -21
  43. magic_hour/resources/v1/lip_sync/README.md +27 -21
  44. magic_hour/resources/v1/lip_sync/client.py +15 -0
  45. magic_hour/resources/v1/photo_colorizer/README.md +10 -11
  46. magic_hour/resources/v1/text_to_video/README.md +15 -17
  47. magic_hour/resources/v1/video_projects/README.md +12 -16
  48. magic_hour/resources/v1/video_to_video/README.md +24 -26
  49. magic_hour/types/models/__init__.py +2 -0
  50. magic_hour/types/models/v1_ai_voice_cloner_create_response.py +27 -0
  51. magic_hour/types/models/v1_audio_projects_get_response.py +1 -1
  52. magic_hour/types/models/v1_video_projects_get_response.py +1 -1
  53. magic_hour/types/params/__init__.py +26 -0
  54. magic_hour/types/params/v1_ai_image_editor_create_body_assets.py +18 -4
  55. magic_hour/types/params/v1_ai_image_editor_create_body_style.py +13 -0
  56. magic_hour/types/params/v1_ai_image_editor_generate_body_assets.py +12 -1
  57. magic_hour/types/params/v1_ai_image_generator_create_body_style.py +16 -0
  58. magic_hour/types/params/v1_ai_talking_photo_create_body_style.py +6 -4
  59. magic_hour/types/params/v1_ai_voice_cloner_create_body.py +49 -0
  60. magic_hour/types/params/v1_ai_voice_cloner_create_body_assets.py +33 -0
  61. magic_hour/types/params/v1_ai_voice_cloner_create_body_style.py +28 -0
  62. magic_hour/types/params/v1_ai_voice_cloner_generate_body_assets.py +28 -0
  63. magic_hour/types/params/v1_ai_voice_generator_create_body_style.py +382 -2
  64. magic_hour/types/params/v1_face_swap_create_body_style.py +1 -1
  65. magic_hour/types/params/v1_files_upload_urls_create_body_items_item.py +1 -1
  66. magic_hour/types/params/v1_lip_sync_create_body.py +12 -0
  67. magic_hour/types/params/v1_lip_sync_create_body_style.py +37 -0
  68. magic_hour/types/params/v1_video_to_video_create_body.py +1 -1
  69. magic_hour/types/params/v1_video_to_video_create_body_style.py +32 -4
  70. {magic_hour-0.40.0.dist-info → magic_hour-0.44.0.dist-info}/METADATA +77 -62
  71. {magic_hour-0.40.0.dist-info → magic_hour-0.44.0.dist-info}/RECORD +73 -63
  72. {magic_hour-0.40.0.dist-info → magic_hour-0.44.0.dist-info}/LICENSE +0 -0
  73. {magic_hour-0.40.0.dist-info → magic_hour-0.44.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,10 @@
1
1
  import typing
2
2
 
3
+ from magic_hour.helpers.logger import get_sdk_logger
4
+ from magic_hour.resources.v1.audio_projects.client import (
5
+ AsyncAudioProjectsClient,
6
+ AudioProjectsClient,
7
+ )
3
8
  from magic_hour.types import models, params
4
9
  from make_api_request import (
5
10
  AsyncBaseClient,
@@ -11,10 +16,70 @@ from make_api_request import (
11
16
  )
12
17
 
13
18
 
19
+ logger = get_sdk_logger(__name__)
20
+
21
+
14
22
  class AiVoiceGeneratorClient:
15
23
  def __init__(self, *, base_client: SyncBaseClient):
16
24
  self._base_client = base_client
17
25
 
26
+ def generate(
27
+ self,
28
+ *,
29
+ style: params.V1AiVoiceGeneratorCreateBodyStyle,
30
+ name: typing.Union[
31
+ typing.Optional[str], type_utils.NotGiven
32
+ ] = type_utils.NOT_GIVEN,
33
+ wait_for_completion: bool = True,
34
+ download_outputs: bool = True,
35
+ download_directory: typing.Optional[str] = None,
36
+ request_options: typing.Optional[RequestOptions] = None,
37
+ ):
38
+ """
39
+ Generate AI voice (alias for create with additional functionality).
40
+
41
+ Generate speech from text. Each character costs 0.05 credits. The cost is rounded up to the nearest whole number.
42
+
43
+ Args:
44
+ style: The content used to generate speech.
45
+ name: The name of audio. This value is mainly used for your own identification of the audio.
46
+ wait_for_completion: Whether to wait for the audio project to complete
47
+ download_outputs: Whether to download the outputs
48
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
49
+ request_options: Additional options to customize the HTTP request
50
+
51
+ Returns:
52
+ V1AudioProjectsGetResponseWithDownloads: The response from the AI Voice Generator API with the downloaded paths if `download_outputs` is True.
53
+
54
+ Examples:
55
+ ```py
56
+ response = client.v1.ai_voice_generator.generate(
57
+ style={"prompt": "Hello, how are you?", "voice_name": "Elon Musk"},
58
+ name="Generated Voice",
59
+ wait_for_completion=True,
60
+ download_outputs=True,
61
+ download_directory="outputs/",
62
+ )
63
+ ```
64
+ """
65
+
66
+ create_response = self.create(
67
+ style=style,
68
+ name=name,
69
+ request_options=request_options,
70
+ )
71
+ logger.info(f"AI Voice Generator response: {create_response}")
72
+
73
+ audio_projects_client = AudioProjectsClient(base_client=self._base_client)
74
+ response = audio_projects_client.check_result(
75
+ id=create_response.id,
76
+ wait_for_completion=wait_for_completion,
77
+ download_outputs=download_outputs,
78
+ download_directory=download_directory,
79
+ )
80
+
81
+ return response
82
+
18
83
  def create(
19
84
  self,
20
85
  *,
@@ -69,6 +134,63 @@ class AsyncAiVoiceGeneratorClient:
69
134
  def __init__(self, *, base_client: AsyncBaseClient):
70
135
  self._base_client = base_client
71
136
 
137
+ async def generate(
138
+ self,
139
+ *,
140
+ style: params.V1AiVoiceGeneratorCreateBodyStyle,
141
+ name: typing.Union[
142
+ typing.Optional[str], type_utils.NotGiven
143
+ ] = type_utils.NOT_GIVEN,
144
+ wait_for_completion: bool = True,
145
+ download_outputs: bool = True,
146
+ download_directory: typing.Optional[str] = None,
147
+ request_options: typing.Optional[RequestOptions] = None,
148
+ ):
149
+ """
150
+ Generate AI voice (alias for create with additional functionality).
151
+
152
+ Generate speech from text. Each character costs 0.05 credits. The cost is rounded up to the nearest whole number.
153
+
154
+ Args:
155
+ style: The content used to generate speech.
156
+ name: The name of audio. This value is mainly used for your own identification of the audio.
157
+ wait_for_completion: Whether to wait for the audio project to complete
158
+ download_outputs: Whether to download the outputs
159
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
160
+ request_options: Additional options to customize the HTTP request
161
+
162
+ Returns:
163
+ V1AudioProjectsGetResponseWithDownloads: The response from the AI Voice Generator API with the downloaded paths if `download_outputs` is True.
164
+
165
+ Examples:
166
+ ```py
167
+ response = await client.v1.ai_voice_generator.generate(
168
+ style={"prompt": "Hello, how are you?", "voice_name": "Elon Musk"},
169
+ name="Generated Voice",
170
+ wait_for_completion=True,
171
+ download_outputs=True,
172
+ download_directory="outputs/",
173
+ )
174
+ ```
175
+ """
176
+
177
+ create_response = await self.create(
178
+ style=style,
179
+ name=name,
180
+ request_options=request_options,
181
+ )
182
+ logger.info(f"AI Voice Generator response: {create_response}")
183
+
184
+ audio_projects_client = AsyncAudioProjectsClient(base_client=self._base_client)
185
+ response = await audio_projects_client.check_result(
186
+ id=create_response.id,
187
+ wait_for_completion=wait_for_completion,
188
+ download_outputs=download_outputs,
189
+ download_directory=download_directory,
190
+ )
191
+
192
+ return response
193
+
72
194
  async def create(
73
195
  self,
74
196
  *,
@@ -2,8 +2,6 @@
2
2
 
3
3
  ## Module Functions
4
4
 
5
-
6
-
7
5
  <!-- CUSTOM DOCS START -->
8
6
 
9
7
  ### Animation Generate Workflow <a name="generate"></a>
@@ -89,6 +87,7 @@ res = await client.v1.animation.generate(
89
87
  ```
90
88
 
91
89
  <!-- CUSTOM DOCS END -->
90
+
92
91
  ### Animation <a name="create"></a>
93
92
 
94
93
  Create a Animation video. The estimated frame cost is calculated based on the `fps` and `end_seconds` input.
@@ -97,25 +96,25 @@ Create a Animation video. The estimated frame cost is calculated based on the `f
97
96
 
98
97
  #### Parameters
99
98
 
100
- | Parameter | Required | Description | Example |
101
- |-----------|:--------:|-------------|--------|
102
- | `assets` | | Provide the assets for animation. | `{"audio_file_path": "api-assets/id/1234.mp3", "audio_source": "file", "image_file_path": "api-assets/id/1234.png"}` |
103
- | `└─ audio_file_path` | | The path of the input audio. This field is required if `audio_source` is `file`. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). Please refer to the [Input File documentation](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) to learn more. | `"api-assets/id/1234.mp3"` |
104
- | `└─ audio_source` | | Optionally add an audio source if you'd like to incorporate audio into your video | `"file"` |
105
- | `└─ image_file_path` | | An initial image to use a the first frame of the video. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). Please refer to the [Input File documentation](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) to learn more. | `"api-assets/id/1234.png"` |
106
- | `└─ youtube_url` | | Using a youtube video as the input source. This field is required if `audio_source` is `youtube` | `"http://www.example.com"` |
107
- | `end_seconds` | | This value determines the duration of the output video. | `15.0` |
108
- | `fps` | | The desire output video frame rate | `12.0` |
109
- | `height` | | The height of the final output video. The maximum height depends on your subscription. Please refer to our [pricing page](https://magichour.ai/pricing) for more details | `960` |
110
- | `style` | | Defines the style of the output video | `{"art_style": "Painterly Illustration", "camera_effect": "Simple Zoom In", "prompt": "Cyberpunk city", "prompt_type": "custom", "transition_speed": 5}` |
111
- | `└─ art_style` | | The art style used to create the output video | `"Painterly Illustration"` |
112
- | `└─ art_style_custom` | | Describe custom art style. This field is required if `art_style` is `Custom` | `"string"` |
113
- | `└─ camera_effect` | | The camera effect used to create the output video | `"Simple Zoom In"` |
114
- | `└─ prompt` | | The prompt used for the video. Prompt is required if `prompt_type` is `custom`. Otherwise this value is ignored | `"Cyberpunk city"` |
115
- | `└─ prompt_type` | | * `custom` - Use your own prompt for the video. * `use_lyrics` - Use the lyrics of the audio to create the prompt. If this option is selected, then `assets.audio_source` must be `file` or `youtube`. * `ai_choose` - Let AI write the prompt. If this option is selected, then `assets.audio_source` must be `file` or `youtube`. | `"custom"` |
116
- | `└─ transition_speed` | | Change determines how quickly the video's content changes across frames. * Higher = more rapid transitions. * Lower = more stable visual experience. | `5` |
117
- | `width` | | The width of the final output video. The maximum width depends on your subscription. Please refer to our [pricing page](https://magichour.ai/pricing) for more details | `512` |
118
- | `name` | | The name of video. This value is mainly used for your own identification of the video. | `"Animation video"` |
99
+ | Parameter | Required | Description | Example |
100
+ | --------------------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
101
+ | `assets` | | Provide the assets for animation. | `{"audio_file_path": "api-assets/id/1234.mp3", "audio_source": "file", "image_file_path": "api-assets/id/1234.png"}` |
102
+ | `└─ audio_file_path` | | The path of the input audio. This field is required if `audio_source` is `file`. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). Please refer to the [Input File documentation](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) to learn more. | `"api-assets/id/1234.mp3"` |
103
+ | `└─ audio_source` | | Optionally add an audio source if you'd like to incorporate audio into your video | `"file"` |
104
+ | `└─ image_file_path` | | An initial image to use a the first frame of the video. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). Please refer to the [Input File documentation](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) to learn more. | `"api-assets/id/1234.png"` |
105
+ | `└─ youtube_url` | | Using a youtube video as the input source. This field is required if `audio_source` is `youtube` | `"http://www.example.com"` |
106
+ | `end_seconds` | | This value determines the duration of the output video. | `15.0` |
107
+ | `fps` | | The desire output video frame rate | `12.0` |
108
+ | `height` | | The height of the final output video. The maximum height depends on your subscription. Please refer to our [pricing page](https://magichour.ai/pricing) for more details | `960` |
109
+ | `style` | | Defines the style of the output video | `{"art_style": "Painterly Illustration", "camera_effect": "Simple Zoom In", "prompt": "Cyberpunk city", "prompt_type": "custom", "transition_speed": 5}` |
110
+ | `└─ art_style` | | The art style used to create the output video | `"Painterly Illustration"` |
111
+ | `└─ art_style_custom` | | Describe custom art style. This field is required if `art_style` is `Custom` | `"string"` |
112
+ | `└─ camera_effect` | | The camera effect used to create the output video | `"Simple Zoom In"` |
113
+ | `└─ prompt` | | The prompt used for the video. Prompt is required if `prompt_type` is `custom`. Otherwise this value is ignored | `"Cyberpunk city"` |
114
+ | `└─ prompt_type` | | * `custom` - Use your own prompt for the video. * `use_lyrics` - Use the lyrics of the audio to create the prompt. If this option is selected, then `assets.audio_source` must be `file` or `youtube`. * `ai_choose` - Let AI write the prompt. If this option is selected, then `assets.audio_source` must be `file` or `youtube`. | `"custom"` |
115
+ | `└─ transition_speed` | | Change determines how quickly the video's content changes across frames. * Higher = more rapid transitions. * Lower = more stable visual experience. | `5` |
116
+ | `width` | | The width of the final output video. The maximum width depends on your subscription. Please refer to our [pricing page](https://magichour.ai/pricing) for more details | `512` |
117
+ | `name` | | The name of video. This value is mainly used for your own identification of the video. | `"Animation video"` |
119
118
 
120
119
  #### Synchronous Client
121
120
 
@@ -143,7 +142,6 @@ res = client.v1.animation.create(
143
142
  width=512,
144
143
  name="Animation video",
145
144
  )
146
-
147
145
  ```
148
146
 
149
147
  #### Asynchronous Client
@@ -172,15 +170,16 @@ res = await client.v1.animation.create(
172
170
  width=512,
173
171
  name="Animation video",
174
172
  )
175
-
176
173
  ```
177
174
 
178
175
  #### Response
179
176
 
180
177
  ##### Type
178
+
181
179
  [V1AnimationCreateResponse](/magic_hour/types/models/v1_animation_create_response.py)
182
180
 
183
181
  ##### Example
184
- `{"credits_charged": 450, "estimated_frame_cost": 450, "id": "cuid-example"}`
185
-
186
182
 
183
+ ```python
184
+ {"credits_charged": 450, "estimated_frame_cost": 450, "id": "cuid-example"}
185
+ ```
@@ -2,6 +2,53 @@
2
2
 
3
3
  ## Module Functions
4
4
 
5
+ <!-- CUSTOM DOCS START -->
6
+
7
+ ### Check results <a name="check-result"></a>
8
+
9
+ Poll the details API to check on the status of the rendering. Optionally can also download the output
10
+
11
+ #### Parameters
12
+
13
+ | Parameter | Required | Description | Example |
14
+ | --------------------- | :------: | ---------------------------------------------------------------------------------------------------- | ---------------- |
15
+ | `id` | ✓ | Unique ID of the audio project. This value is returned by all of the POST APIs that create an audio. | `"cuid-example"` |
16
+ | `wait_for_completion` | ✗ | Whether to wait for the project to complete. | `True` |
17
+ | `download_outputs` | ✗ | Whether to download the generated files | `True` |
18
+ | `download_directory` | ✗ | Directory to save downloaded files (defaults to current directory) | `"./outputs"` |
19
+
20
+ #### Synchronous Client
21
+
22
+ ```python
23
+ from magic_hour import Client
24
+ from os import getenv
25
+
26
+ client = Client(token=getenv("API_TOKEN"))
27
+ res = client.v1.audio_projects.check_result(
28
+ id="cuid-example",
29
+ wait_for_completion=True,
30
+ download_outputs=True,
31
+ download_directory="outputs",
32
+ )
33
+ ```
34
+
35
+ #### Asynchronous Client
36
+
37
+ ```python
38
+ from magic_hour import AsyncClient
39
+ from os import getenv
40
+
41
+ client = AsyncClient(token=getenv("API_TOKEN"))
42
+ res = await client.v1.audio_projects.check_result(
43
+ id="cuid-example",
44
+ wait_for_completion=True,
45
+ download_outputs=True,
46
+ download_directory="outputs",
47
+ )
48
+ ```
49
+
50
+ <!-- CUSTOM DOCS END -->
51
+
5
52
  ### Delete audio <a name="delete"></a>
6
53
 
7
54
  Permanently delete the rendered audio file(s). This action is not reversible, please be sure before deleting.
@@ -10,9 +57,9 @@ Permanently delete the rendered audio file(s). This action is not reversible, pl
10
57
 
11
58
  #### Parameters
12
59
 
13
- | Parameter | Required | Description | Example |
14
- |-----------|:--------:|-------------|--------|
15
- | `id` | | Unique ID of the audio project. This value is returned by all of the POST APIs that create an audio. | `"cuid-example"` |
60
+ | Parameter | Required | Description | Example |
61
+ | --------- | :------: | ---------------------------------------------------------------------------------------------------- | ---------------- |
62
+ | `id` | | Unique ID of the audio project. This value is returned by all of the POST APIs that create an audio. | `"cuid-example"` |
16
63
 
17
64
  #### Synchronous Client
18
65
 
@@ -22,7 +69,6 @@ from os import getenv
22
69
 
23
70
  client = Client(token=getenv("API_TOKEN"))
24
71
  res = client.v1.audio_projects.delete(id="cuid-example")
25
-
26
72
  ```
27
73
 
28
74
  #### Asynchronous Client
@@ -33,7 +79,6 @@ from os import getenv
33
79
 
34
80
  client = AsyncClient(token=getenv("API_TOKEN"))
35
81
  res = await client.v1.audio_projects.delete(id="cuid-example")
36
-
37
82
  ```
38
83
 
39
84
  ### Get audio details <a name="get"></a>
@@ -41,6 +86,7 @@ res = await client.v1.audio_projects.delete(id="cuid-example")
41
86
  Get the details of a audio project. The `downloads` field will be empty unless the audio was successfully rendered.
42
87
 
43
88
  The audio can be one of the following status
89
+
44
90
  - `draft` - not currently used
45
91
  - `queued` - the job is queued and waiting for a GPU
46
92
  - `rendering` - the generation is in progress
@@ -48,14 +94,13 @@ The audio can be one of the following status
48
94
  - `error` - an error occurred during rendering
49
95
  - `canceled` - audio render is canceled by the user
50
96
 
51
-
52
97
  **API Endpoint**: `GET /v1/audio-projects/{id}`
53
98
 
54
99
  #### Parameters
55
100
 
56
- | Parameter | Required | Description | Example |
57
- |-----------|:--------:|-------------|--------|
58
- | `id` | | Unique ID of the audio project. This value is returned by all of the POST APIs that create an audio. | `"cuid-example"` |
101
+ | Parameter | Required | Description | Example |
102
+ | --------- | :------: | ---------------------------------------------------------------------------------------------------- | ---------------- |
103
+ | `id` | | Unique ID of the audio project. This value is returned by all of the POST APIs that create an audio. | `"cuid-example"` |
59
104
 
60
105
  #### Synchronous Client
61
106
 
@@ -65,7 +110,6 @@ from os import getenv
65
110
 
66
111
  client = Client(token=getenv("API_TOKEN"))
67
112
  res = client.v1.audio_projects.get(id="cuid-example")
68
-
69
113
  ```
70
114
 
71
115
  #### Asynchronous Client
@@ -76,15 +120,16 @@ from os import getenv
76
120
 
77
121
  client = AsyncClient(token=getenv("API_TOKEN"))
78
122
  res = await client.v1.audio_projects.get(id="cuid-example")
79
-
80
123
  ```
81
124
 
82
125
  #### Response
83
126
 
84
127
  ##### Type
128
+
85
129
  [V1AudioProjectsGetResponse](/magic_hour/types/models/v1_audio_projects_get_response.py)
86
130
 
87
131
  ##### Example
88
- `{"created_at": "1970-01-01T00:00:00", "credits_charged": 2, "downloads": [{"expires_at": "2024-10-19T05:16:19.027Z", "url": "https://videos.magichour.ai/id/output.wav"}], "enabled": True, "error": {"code": "no_source_face", "message": "Please use an image with a detectable face"}, "id": "cuid-example", "name": "Example Name", "status": "complete", "type_": "VOICE_GENERATOR"}`
89
-
90
132
 
133
+ ```python
134
+ {"created_at": "1970-01-01T00:00:00", "credits_charged": 2, "downloads": [{"expires_at": "2024-10-19T05:16:19.027Z", "url": "https://videos.magichour.ai/id/output.wav"}], "enabled": True, "error": {"code": "no_source_face", "message": "Please use an image with a detectable face"}, "id": "cuid-example", "name": "Example Name", "status": "complete", "type_": "VOICE_GENERATOR"}
135
+ ```
@@ -1,4 +1,12 @@
1
- from .client import AsyncAudioProjectsClient, AudioProjectsClient
1
+ from .client import (
2
+ AsyncAudioProjectsClient,
3
+ AudioProjectsClient,
4
+ V1AudioProjectsGetResponseWithDownloads,
5
+ )
2
6
 
3
7
 
4
- __all__ = ["AsyncAudioProjectsClient", "AudioProjectsClient"]
8
+ __all__ = [
9
+ "AsyncAudioProjectsClient",
10
+ "AudioProjectsClient",
11
+ "V1AudioProjectsGetResponseWithDownloads",
12
+ ]
@@ -1,5 +1,10 @@
1
+ import os
2
+ import pydantic
3
+ import time
1
4
  import typing
2
5
 
6
+ from magic_hour.helpers.download import download_files_async, download_files_sync
7
+ from magic_hour.helpers.logger import get_sdk_logger
3
8
  from magic_hour.types import models
4
9
  from make_api_request import (
5
10
  AsyncBaseClient,
@@ -9,10 +14,83 @@ from make_api_request import (
9
14
  )
10
15
 
11
16
 
17
+ logger = get_sdk_logger(__name__)
18
+
19
+
20
+ class V1AudioProjectsGetResponseWithDownloads(models.V1AudioProjectsGetResponse):
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 audio project is complete.
28
+ """
29
+
30
+
12
31
  class AudioProjectsClient:
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
+ ) -> V1AudioProjectsGetResponseWithDownloads:
42
+ """
43
+ Check the result of an audio project with optional waiting and downloading.
44
+
45
+ This method retrieves the status of an audio project and optionally waits for completion
46
+ and downloads the output files.
47
+
48
+ Args:
49
+ id: Unique ID of the audio project
50
+ wait_for_completion: Whether to wait for the audio 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
+ V1AudioProjectsGetResponseWithDownloads: The audio 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 = V1AudioProjectsGetResponseWithDownloads(
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"Audio project {id} has status {api_response.status}: {api_response.error}"
79
+ )
80
+ return V1AudioProjectsGetResponseWithDownloads(**api_response.model_dump())
81
+
82
+ if not download_outputs:
83
+ return V1AudioProjectsGetResponseWithDownloads(**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 V1AudioProjectsGetResponseWithDownloads(
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 AsyncAudioProjectsClient:
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
+ ) -> V1AudioProjectsGetResponseWithDownloads:
183
+ """
184
+ Check the result of an audio project with optional waiting and downloading.
185
+
186
+ This method retrieves the status of an audio project and optionally waits for completion
187
+ and downloads the output files.
188
+
189
+ Args:
190
+ id: Unique ID of the audio project
191
+ wait_for_completion: Whether to wait for the audio 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
+ V1AudioProjectsGetResponseWithDownloads: The audio 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 = V1AudioProjectsGetResponseWithDownloads(
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"Audio project {id} has status {api_response.status}: {api_response.error}"
220
+ )
221
+ return V1AudioProjectsGetResponseWithDownloads(**api_response.model_dump())
222
+
223
+ if not download_outputs:
224
+ return V1AudioProjectsGetResponseWithDownloads(**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 V1AudioProjectsGetResponseWithDownloads(
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: