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.

Files changed (97) hide show
  1. magic_hour/README.md +35 -0
  2. magic_hour/core/base_client.py +6 -5
  3. magic_hour/core/query.py +12 -6
  4. magic_hour/core/request.py +3 -3
  5. magic_hour/core/response.py +18 -14
  6. magic_hour/core/utils.py +3 -3
  7. magic_hour/environment.py +1 -1
  8. magic_hour/helpers/__init__.py +3 -0
  9. magic_hour/helpers/download.py +75 -0
  10. magic_hour/resources/v1/README.md +33 -0
  11. magic_hour/resources/v1/ai_clothes_changer/README.md +73 -0
  12. magic_hour/resources/v1/ai_clothes_changer/client.py +146 -0
  13. magic_hour/resources/v1/ai_face_editor/README.md +110 -0
  14. magic_hour/resources/v1/ai_face_editor/client.py +168 -0
  15. magic_hour/resources/v1/ai_gif_generator/README.md +59 -0
  16. magic_hour/resources/v1/ai_gif_generator/client.py +119 -0
  17. magic_hour/resources/v1/ai_headshot_generator/README.md +60 -0
  18. magic_hour/resources/v1/ai_headshot_generator/client.py +140 -0
  19. magic_hour/resources/v1/ai_image_editor/README.md +64 -0
  20. magic_hour/resources/v1/ai_image_editor/client.py +136 -0
  21. magic_hour/resources/v1/ai_image_generator/README.md +66 -0
  22. magic_hour/resources/v1/ai_image_generator/client.py +139 -0
  23. magic_hour/resources/v1/ai_image_upscaler/README.md +67 -0
  24. magic_hour/resources/v1/ai_image_upscaler/client.py +150 -0
  25. magic_hour/resources/v1/ai_meme_generator/README.md +71 -0
  26. magic_hour/resources/v1/ai_meme_generator/client.py +127 -0
  27. magic_hour/resources/v1/ai_photo_editor/README.md +98 -7
  28. magic_hour/resources/v1/ai_photo_editor/client.py +174 -0
  29. magic_hour/resources/v1/ai_qr_code_generator/README.md +63 -0
  30. magic_hour/resources/v1/ai_qr_code_generator/client.py +123 -0
  31. magic_hour/resources/v1/ai_talking_photo/README.md +74 -0
  32. magic_hour/resources/v1/ai_talking_photo/client.py +170 -0
  33. magic_hour/resources/v1/animation/README.md +100 -0
  34. magic_hour/resources/v1/animation/client.py +218 -0
  35. magic_hour/resources/v1/auto_subtitle_generator/README.md +69 -0
  36. magic_hour/resources/v1/auto_subtitle_generator/client.py +178 -0
  37. magic_hour/resources/v1/face_detection/README.md +59 -0
  38. magic_hour/resources/v1/face_detection/__init__.py +10 -2
  39. magic_hour/resources/v1/face_detection/client.py +179 -0
  40. magic_hour/resources/v1/face_swap/README.md +105 -12
  41. magic_hour/resources/v1/face_swap/client.py +262 -28
  42. magic_hour/resources/v1/face_swap_photo/README.md +84 -0
  43. magic_hour/resources/v1/face_swap_photo/client.py +172 -0
  44. magic_hour/resources/v1/files/README.md +6 -0
  45. magic_hour/resources/v1/files/client.py +350 -0
  46. magic_hour/resources/v1/files/client_test.py +414 -0
  47. magic_hour/resources/v1/files/upload_urls/README.md +8 -0
  48. magic_hour/resources/v1/image_background_remover/README.md +68 -0
  49. magic_hour/resources/v1/image_background_remover/client.py +130 -0
  50. magic_hour/resources/v1/image_projects/README.md +8 -0
  51. magic_hour/resources/v1/image_projects/__init__.py +10 -2
  52. magic_hour/resources/v1/image_projects/client.py +138 -0
  53. magic_hour/resources/v1/image_projects/client_test.py +527 -0
  54. magic_hour/resources/v1/image_to_video/README.md +77 -9
  55. magic_hour/resources/v1/image_to_video/client.py +210 -8
  56. magic_hour/resources/v1/lip_sync/README.md +87 -13
  57. magic_hour/resources/v1/lip_sync/client.py +230 -28
  58. magic_hour/resources/v1/photo_colorizer/README.md +59 -0
  59. magic_hour/resources/v1/photo_colorizer/client.py +130 -0
  60. magic_hour/resources/v1/text_to_video/README.md +68 -0
  61. magic_hour/resources/v1/text_to_video/client.py +151 -0
  62. magic_hour/resources/v1/video_projects/README.md +8 -0
  63. magic_hour/resources/v1/video_projects/__init__.py +10 -2
  64. magic_hour/resources/v1/video_projects/client.py +137 -0
  65. magic_hour/resources/v1/video_projects/client_test.py +527 -0
  66. magic_hour/resources/v1/video_to_video/README.md +98 -14
  67. magic_hour/resources/v1/video_to_video/client.py +242 -28
  68. magic_hour/types/params/__init__.py +58 -0
  69. magic_hour/types/params/v1_ai_clothes_changer_generate_body_assets.py +33 -0
  70. magic_hour/types/params/v1_ai_face_editor_generate_body_assets.py +17 -0
  71. magic_hour/types/params/v1_ai_headshot_generator_generate_body_assets.py +17 -0
  72. magic_hour/types/params/v1_ai_image_editor_generate_body_assets.py +17 -0
  73. magic_hour/types/params/v1_ai_image_upscaler_generate_body_assets.py +17 -0
  74. magic_hour/types/params/v1_ai_photo_editor_generate_body_assets.py +17 -0
  75. magic_hour/types/params/v1_ai_talking_photo_generate_body_assets.py +26 -0
  76. magic_hour/types/params/v1_animation_generate_body_assets.py +39 -0
  77. magic_hour/types/params/v1_auto_subtitle_generator_generate_body_assets.py +17 -0
  78. magic_hour/types/params/v1_face_detection_generate_body_assets.py +17 -0
  79. magic_hour/types/params/v1_face_swap_create_body.py +24 -14
  80. magic_hour/types/params/v1_face_swap_create_body_style.py +33 -0
  81. magic_hour/types/params/v1_face_swap_generate_body_assets.py +56 -0
  82. magic_hour/types/params/v1_face_swap_generate_body_assets_face_mappings_item.py +25 -0
  83. magic_hour/types/params/v1_face_swap_photo_generate_body_assets.py +47 -0
  84. magic_hour/types/params/v1_face_swap_photo_generate_body_assets_face_mappings_item.py +25 -0
  85. magic_hour/types/params/v1_image_background_remover_generate_body_assets.py +27 -0
  86. magic_hour/types/params/v1_image_to_video_create_body.py +14 -6
  87. magic_hour/types/params/v1_image_to_video_generate_body_assets.py +17 -0
  88. magic_hour/types/params/v1_lip_sync_create_body.py +12 -14
  89. magic_hour/types/params/v1_lip_sync_generate_body_assets.py +36 -0
  90. magic_hour/types/params/v1_photo_colorizer_generate_body_assets.py +17 -0
  91. magic_hour/types/params/v1_video_to_video_create_body.py +12 -14
  92. magic_hour/types/params/v1_video_to_video_generate_body_assets.py +27 -0
  93. magic_hour-0.36.0.dist-info/METADATA +303 -0
  94. {magic_hour-0.34.0.dist-info → magic_hour-0.36.0.dist-info}/RECORD +96 -68
  95. magic_hour-0.34.0.dist-info/METADATA +0 -166
  96. {magic_hour-0.34.0.dist-info → magic_hour-0.36.0.dist-info}/LICENSE +0 -0
  97. {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
 
3
4
  from magic_hour.core import (
@@ -8,13 +9,121 @@ from magic_hour.core import (
8
9
  to_encodable,
9
10
  type_utils,
10
11
  )
12
+ from magic_hour.resources.v1.files.client import AsyncFilesClient, FilesClient
13
+ from magic_hour.resources.v1.video_projects.client import (
14
+ AsyncVideoProjectsClient,
15
+ VideoProjectsClient,
16
+ )
11
17
  from magic_hour.types import models, params
12
18
 
13
19
 
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+
14
24
  class LipSyncClient:
15
25
  def __init__(self, *, base_client: SyncBaseClient):
16
26
  self._base_client = base_client
17
27
 
28
+ def generate(
29
+ self,
30
+ *,
31
+ assets: params.V1LipSyncGenerateBodyAssets,
32
+ end_seconds: float,
33
+ start_seconds: float,
34
+ height: typing.Union[
35
+ typing.Optional[int], type_utils.NotGiven
36
+ ] = type_utils.NOT_GIVEN,
37
+ max_fps_limit: typing.Union[
38
+ typing.Optional[float], type_utils.NotGiven
39
+ ] = type_utils.NOT_GIVEN,
40
+ name: typing.Union[
41
+ typing.Optional[str], type_utils.NotGiven
42
+ ] = type_utils.NOT_GIVEN,
43
+ width: typing.Union[
44
+ typing.Optional[int], type_utils.NotGiven
45
+ ] = type_utils.NOT_GIVEN,
46
+ wait_for_completion: bool = True,
47
+ download_outputs: bool = True,
48
+ download_directory: typing.Optional[str] = None,
49
+ request_options: typing.Optional[RequestOptions] = None,
50
+ ):
51
+ """
52
+ Generate lip sync video (alias for create with additional functionality).
53
+
54
+ Create a Lip Sync 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.
55
+
56
+ Args:
57
+ height: `height` is deprecated and no longer influences the output video's resolution.
58
+ max_fps_limit: Defines the maximum FPS (frames per second) for the output video. If the input video's FPS is lower than this limit, the output video will retain the input FPS. This is useful for reducing unnecessary frame usage in scenarios where high FPS is not required.
59
+ name: The name of video. This value is mainly used for your own identification of the video.
60
+ width: `width` is deprecated and no longer influences the output video's resolution.
61
+ assets: Provide the assets for lip-sync. For video, The `video_source` field determines whether `video_file_path` or `youtube_url` field is used
62
+ end_seconds: The end time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.1, and more than the start_seconds.
63
+ start_seconds: The start time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.
64
+ wait_for_completion: Whether to wait for the video project to complete
65
+ download_outputs: Whether to download the outputs
66
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
67
+ request_options: Additional options to customize the HTTP request
68
+
69
+ Returns:
70
+ V1VideoProjectsGetResponseWithDownloads: The response from the Lip Sync API with the downloaded paths if `download_outputs` is True.
71
+
72
+ Examples:
73
+ ```py
74
+ response = client.v1.lip_sync.generate(
75
+ assets={
76
+ "audio_file_path": "path/to/audio.mp3",
77
+ "video_file_path": "path/to/video.mp4",
78
+ "video_source": "file",
79
+ },
80
+ end_seconds=15.0,
81
+ start_seconds=0.0,
82
+ max_fps_limit=12.0,
83
+ wait_for_completion=True,
84
+ download_outputs=True,
85
+ download_directory="outputs/",
86
+ )
87
+ ```
88
+ """
89
+
90
+ file_client = FilesClient(base_client=self._base_client)
91
+
92
+ # Upload audio file (always required)
93
+ audio_file_path = assets["audio_file_path"]
94
+ assets["audio_file_path"] = file_client.upload_file(file=audio_file_path)
95
+
96
+ # Upload video file if video_source is "file" and video_file_path is provided
97
+ if (
98
+ assets.get("video_source") == "file"
99
+ and "video_file_path" in assets
100
+ and assets["video_file_path"]
101
+ ):
102
+ video_file_path = assets["video_file_path"]
103
+ assets["video_file_path"] = file_client.upload_file(file=video_file_path)
104
+
105
+ create_response = self.create(
106
+ assets=assets,
107
+ end_seconds=end_seconds,
108
+ start_seconds=start_seconds,
109
+ height=height,
110
+ max_fps_limit=max_fps_limit,
111
+ name=name,
112
+ width=width,
113
+ request_options=request_options,
114
+ )
115
+ logger.info(f"Lip Sync response: {create_response}")
116
+
117
+ video_projects_client = VideoProjectsClient(base_client=self._base_client)
118
+ response = video_projects_client.check_result(
119
+ id=create_response.id,
120
+ wait_for_completion=wait_for_completion,
121
+ download_outputs=download_outputs,
122
+ download_directory=download_directory,
123
+ )
124
+
125
+ return response
126
+
18
127
  def create(
19
128
  self,
20
129
  *,
@@ -46,24 +155,22 @@ class LipSyncClient:
46
155
  POST /v1/lip-sync
47
156
 
48
157
  Args:
49
- height: Used to determine the dimensions of the output video.
50
-
51
- * If height is provided, width will also be required. The larger value between width and height will be used to determine the maximum output resolution while maintaining the original aspect ratio.
52
- * If both height and width are omitted, the video will be resized according to your subscription's maximum resolution, while preserving aspect ratio.
158
+ height: `height` is deprecated and no longer influences the output video's resolution.
53
159
 
54
- Note: if the video's original resolution is less than the maximum, the video will not be resized.
160
+ Output resolution is determined by the **minimum** of:
161
+ - The resolution of the input video
162
+ - The maximum resolution allowed by your subscription tier. See our [pricing page](https://magichour.ai/pricing) for more details.
55
163
 
56
- See our [pricing page](https://magichour.ai/pricing) for more details.
164
+ This field is retained only for backward compatibility and will be removed in a future release.
57
165
  max_fps_limit: Defines the maximum FPS (frames per second) for the output video. If the input video's FPS is lower than this limit, the output video will retain the input FPS. This is useful for reducing unnecessary frame usage in scenarios where high FPS is not required.
58
166
  name: The name of video. This value is mainly used for your own identification of the video.
59
- width: Used to determine the dimensions of the output video.
167
+ width: `width` is deprecated and no longer influences the output video's resolution.
60
168
 
61
- * If width is provided, height will also be required. The larger value between width and height will be used to determine the maximum output resolution while maintaining the original aspect ratio.
62
- * If both height and width are omitted, the video will be resized according to your subscription's maximum resolution, while preserving aspect ratio.
169
+ Output resolution is determined by the **minimum** of:
170
+ - The resolution of the input video
171
+ - The maximum resolution allowed by your subscription tier. See our [pricing page](https://magichour.ai/pricing) for more details.
63
172
 
64
- Note: if the video's original resolution is less than the maximum, the video will not be resized.
65
-
66
- See our [pricing page](https://magichour.ai/pricing) for more details.
173
+ This field is retained only for backward compatibility and will be removed in a future release.
67
174
  assets: Provide the assets for lip-sync. For video, The `video_source` field determines whether `video_file_path` or `youtube_url` field is used
68
175
  end_seconds: The end time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.1, and more than the start_seconds.
69
176
  start_seconds: The start time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.
@@ -86,10 +193,8 @@ class LipSyncClient:
86
193
  },
87
194
  end_seconds=15.0,
88
195
  start_seconds=0.0,
89
- height=960,
90
196
  max_fps_limit=12.0,
91
197
  name="Lip Sync video",
92
- width=512,
93
198
  )
94
199
  ```
95
200
  """
@@ -119,6 +224,107 @@ class AsyncLipSyncClient:
119
224
  def __init__(self, *, base_client: AsyncBaseClient):
120
225
  self._base_client = base_client
121
226
 
227
+ async def generate(
228
+ self,
229
+ *,
230
+ assets: params.V1LipSyncGenerateBodyAssets,
231
+ end_seconds: float,
232
+ start_seconds: float,
233
+ height: typing.Union[
234
+ typing.Optional[int], type_utils.NotGiven
235
+ ] = type_utils.NOT_GIVEN,
236
+ max_fps_limit: typing.Union[
237
+ typing.Optional[float], type_utils.NotGiven
238
+ ] = type_utils.NOT_GIVEN,
239
+ name: typing.Union[
240
+ typing.Optional[str], type_utils.NotGiven
241
+ ] = type_utils.NOT_GIVEN,
242
+ width: typing.Union[
243
+ typing.Optional[int], type_utils.NotGiven
244
+ ] = type_utils.NOT_GIVEN,
245
+ wait_for_completion: bool = True,
246
+ download_outputs: bool = True,
247
+ download_directory: typing.Optional[str] = None,
248
+ request_options: typing.Optional[RequestOptions] = None,
249
+ ):
250
+ """
251
+ Generate lip sync video (alias for create with additional functionality).
252
+
253
+ Create a Lip Sync 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.
254
+
255
+ Args:
256
+ height: `height` is deprecated and no longer influences the output video's resolution.
257
+ max_fps_limit: Defines the maximum FPS (frames per second) for the output video. If the input video's FPS is lower than this limit, the output video will retain the input FPS. This is useful for reducing unnecessary frame usage in scenarios where high FPS is not required.
258
+ name: The name of video. This value is mainly used for your own identification of the video.
259
+ width: `width` is deprecated and no longer influences the output video's resolution.
260
+ assets: Provide the assets for lip-sync. For video, The `video_source` field determines whether `video_file_path` or `youtube_url` field is used
261
+ end_seconds: The end time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.1, and more than the start_seconds.
262
+ start_seconds: The start time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.
263
+ wait_for_completion: Whether to wait for the video project to complete
264
+ download_outputs: Whether to download the outputs
265
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
266
+ request_options: Additional options to customize the HTTP request
267
+
268
+ Returns:
269
+ V1VideoProjectsGetResponseWithDownloads: The response from the Lip Sync API with the downloaded paths if `download_outputs` is True.
270
+
271
+ Examples:
272
+ ```py
273
+ response = await client.v1.lip_sync.generate(
274
+ assets={
275
+ "audio_file_path": "path/to/audio.mp3",
276
+ "video_file_path": "path/to/video.mp4",
277
+ "video_source": "file",
278
+ },
279
+ end_seconds=15.0,
280
+ start_seconds=0.0,
281
+ max_fps_limit=12.0,
282
+ wait_for_completion=True,
283
+ download_outputs=True,
284
+ download_directory="outputs/",
285
+ )
286
+ ```
287
+ """
288
+
289
+ file_client = AsyncFilesClient(base_client=self._base_client)
290
+
291
+ # Upload audio file (always required)
292
+ audio_file_path = assets["audio_file_path"]
293
+ assets["audio_file_path"] = await file_client.upload_file(file=audio_file_path)
294
+
295
+ # Upload video file if video_source is "file" and video_file_path is provided
296
+ if (
297
+ assets.get("video_source") == "file"
298
+ and "video_file_path" in assets
299
+ and assets["video_file_path"]
300
+ ):
301
+ video_file_path = assets["video_file_path"]
302
+ assets["video_file_path"] = await file_client.upload_file(
303
+ file=video_file_path
304
+ )
305
+
306
+ create_response = await self.create(
307
+ assets=assets,
308
+ end_seconds=end_seconds,
309
+ start_seconds=start_seconds,
310
+ height=height,
311
+ max_fps_limit=max_fps_limit,
312
+ name=name,
313
+ width=width,
314
+ request_options=request_options,
315
+ )
316
+ logger.info(f"Lip Sync response: {create_response}")
317
+
318
+ video_projects_client = AsyncVideoProjectsClient(base_client=self._base_client)
319
+ response = await video_projects_client.check_result(
320
+ id=create_response.id,
321
+ wait_for_completion=wait_for_completion,
322
+ download_outputs=download_outputs,
323
+ download_directory=download_directory,
324
+ )
325
+
326
+ return response
327
+
122
328
  async def create(
123
329
  self,
124
330
  *,
@@ -150,24 +356,22 @@ class AsyncLipSyncClient:
150
356
  POST /v1/lip-sync
151
357
 
152
358
  Args:
153
- height: Used to determine the dimensions of the output video.
154
-
155
- * If height is provided, width will also be required. The larger value between width and height will be used to determine the maximum output resolution while maintaining the original aspect ratio.
156
- * If both height and width are omitted, the video will be resized according to your subscription's maximum resolution, while preserving aspect ratio.
359
+ height: `height` is deprecated and no longer influences the output video's resolution.
157
360
 
158
- Note: if the video's original resolution is less than the maximum, the video will not be resized.
361
+ Output resolution is determined by the **minimum** of:
362
+ - The resolution of the input video
363
+ - The maximum resolution allowed by your subscription tier. See our [pricing page](https://magichour.ai/pricing) for more details.
159
364
 
160
- See our [pricing page](https://magichour.ai/pricing) for more details.
365
+ This field is retained only for backward compatibility and will be removed in a future release.
161
366
  max_fps_limit: Defines the maximum FPS (frames per second) for the output video. If the input video's FPS is lower than this limit, the output video will retain the input FPS. This is useful for reducing unnecessary frame usage in scenarios where high FPS is not required.
162
367
  name: The name of video. This value is mainly used for your own identification of the video.
163
- width: Used to determine the dimensions of the output video.
164
-
165
- * If width is provided, height will also be required. The larger value between width and height will be used to determine the maximum output resolution while maintaining the original aspect ratio.
166
- * If both height and width are omitted, the video will be resized according to your subscription's maximum resolution, while preserving aspect ratio.
368
+ width: `width` is deprecated and no longer influences the output video's resolution.
167
369
 
168
- Note: if the video's original resolution is less than the maximum, the video will not be resized.
370
+ Output resolution is determined by the **minimum** of:
371
+ - The resolution of the input video
372
+ - The maximum resolution allowed by your subscription tier. See our [pricing page](https://magichour.ai/pricing) for more details.
169
373
 
170
- See our [pricing page](https://magichour.ai/pricing) for more details.
374
+ This field is retained only for backward compatibility and will be removed in a future release.
171
375
  assets: Provide the assets for lip-sync. For video, The `video_source` field determines whether `video_file_path` or `youtube_url` field is used
172
376
  end_seconds: The end time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.1, and more than the start_seconds.
173
377
  start_seconds: The start time of the input video in seconds. This value is used to trim the input video. The value must be greater than 0.
@@ -190,10 +394,8 @@ class AsyncLipSyncClient:
190
394
  },
191
395
  end_seconds=15.0,
192
396
  start_seconds=0.0,
193
- height=960,
194
397
  max_fps_limit=12.0,
195
398
  name="Lip Sync video",
196
- width=512,
197
399
  )
198
400
  ```
199
401
  """
@@ -1,3 +1,60 @@
1
+ # v1_photo_colorizer
2
+
3
+ ## Module Functions
4
+
5
+ <!-- CUSTOM DOCS START -->
6
+
7
+ ### Photo Colorizer Generate Workflow <a name="generate"></a>
8
+
9
+ The workflow performs the following action
10
+
11
+ 1. upload local assets to Magic Hour storage. So you can pass in a local path instead of having to upload files yourself
12
+ 2. trigger a generation
13
+ 3. poll for a completion status. This is configurable
14
+ 4. if success, download the output to local directory
15
+
16
+ > [!TIP]
17
+ > This is the recommended way to use the SDK unless you have specific needs where it is necessary to split up the actions.
18
+
19
+ #### Parameters
20
+
21
+ In Additional to the parameters listed in the `.create` section below, `.generate` introduces 3 new parameters:
22
+
23
+ - `wait_for_completion` (bool, default True): Whether to wait for the project to complete.
24
+ - `download_outputs` (bool, default True): Whether to download the generated files
25
+ - `download_directory` (str, optional): Directory to save downloaded files (defaults to current directory)
26
+
27
+ #### Synchronous Client
28
+
29
+ ```python
30
+ from magic_hour import Client
31
+ from os import getenv
32
+
33
+ client = Client(token=getenv("API_TOKEN"))
34
+ res = client.v1.photo_colorizer.generate(
35
+ assets={"image_file_path": "/path/to/1234.png"}, name="Photo Colorizer image"
36
+ wait_for_completion=True,
37
+ download_outputs=True,
38
+ download_directory="outputs"
39
+ )
40
+ ```
41
+
42
+ #### Asynchronous Client
43
+
44
+ ```python
45
+ from magic_hour import AsyncClient
46
+ from os import getenv
47
+
48
+ client = AsyncClient(token=getenv("API_TOKEN"))
49
+ res = await client.v1.photo_colorizer.generate(
50
+ assets={"image_file_path": "/path/to/1234.png"}, name="Photo Colorizer image"
51
+ wait_for_completion=True,
52
+ download_outputs=True,
53
+ download_directory="outputs"
54
+ )
55
+ ```
56
+
57
+ <!-- CUSTOM DOCS END -->
1
58
 
2
59
  ### Photo Colorizer <a name="create"></a>
3
60
 
@@ -10,6 +67,7 @@ Colorize image. Each image costs 5 credits.
10
67
  | Parameter | Required | Description | Example |
11
68
  |-----------|:--------:|-------------|--------|
12
69
  | `assets` | ✓ | Provide the assets for photo colorization | `{"image_file_path": "api-assets/id/1234.png"}` |
70
+ | `└─ image_file_path` | ✓ | The image used to generate the colorized image. 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"` |
13
71
  | `name` | ✗ | The name of image. This value is mainly used for your own identification of the image. | `"Photo Colorizer image"` |
14
72
 
15
73
  #### Synchronous Client
@@ -45,3 +103,4 @@ res = await client.v1.photo_colorizer.create(
45
103
 
46
104
  ##### Example
47
105
  `{"credits_charged": 5, "frame_cost": 5, "id": "cuid-example"}`
106
+
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import typing
2
3
 
3
4
  from magic_hour.core import (
@@ -8,13 +9,82 @@ from magic_hour.core import (
8
9
  to_encodable,
9
10
  type_utils,
10
11
  )
12
+ from magic_hour.resources.v1.files.client import AsyncFilesClient, FilesClient
13
+ from magic_hour.resources.v1.image_projects.client import (
14
+ AsyncImageProjectsClient,
15
+ ImageProjectsClient,
16
+ )
11
17
  from magic_hour.types import models, params
12
18
 
13
19
 
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+
14
24
  class PhotoColorizerClient:
15
25
  def __init__(self, *, base_client: SyncBaseClient):
16
26
  self._base_client = base_client
17
27
 
28
+ def generate(
29
+ self,
30
+ *,
31
+ assets: params.V1PhotoColorizerGenerateBodyAssets,
32
+ name: typing.Union[
33
+ typing.Optional[str], type_utils.NotGiven
34
+ ] = type_utils.NOT_GIVEN,
35
+ wait_for_completion: bool = True,
36
+ download_outputs: bool = True,
37
+ download_directory: typing.Optional[str] = None,
38
+ request_options: typing.Optional[RequestOptions] = None,
39
+ ):
40
+ """
41
+ Generate colorized photo (alias for create with additional functionality).
42
+
43
+ Colorize image. Each image costs 5 credits.
44
+
45
+ Args:
46
+ name: The name of image. This value is mainly used for your own identification of the image.
47
+ assets: Provide the assets for photo colorization
48
+ wait_for_completion: Whether to wait for the image project to complete
49
+ download_outputs: Whether to download the outputs
50
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
51
+ request_options: Additional options to customize the HTTP request
52
+
53
+ Returns:
54
+ V1ImageProjectsGetResponseWithDownloads: The response from the Photo Colorizer API with the downloaded paths if `download_outputs` is True.
55
+
56
+ Examples:
57
+ ```py
58
+ response = client.v1.photo_colorizer.generate(
59
+ assets={"image_file_path": "path/to/black_white_photo.jpg"},
60
+ name="Colorized Photo",
61
+ wait_for_completion=True,
62
+ download_outputs=True,
63
+ download_directory="outputs/",
64
+ )
65
+ ```
66
+ """
67
+
68
+ file_client = FilesClient(base_client=self._base_client)
69
+
70
+ image_file_path = assets["image_file_path"]
71
+ assets["image_file_path"] = file_client.upload_file(file=image_file_path)
72
+
73
+ create_response = self.create(
74
+ assets=assets, name=name, request_options=request_options
75
+ )
76
+ logger.info(f"Photo Colorizer response: {create_response}")
77
+
78
+ image_projects_client = ImageProjectsClient(base_client=self._base_client)
79
+ response = image_projects_client.check_result(
80
+ id=create_response.id,
81
+ wait_for_completion=wait_for_completion,
82
+ download_outputs=download_outputs,
83
+ download_directory=download_directory,
84
+ )
85
+
86
+ return response
87
+
18
88
  def create(
19
89
  self,
20
90
  *,
@@ -69,6 +139,66 @@ class AsyncPhotoColorizerClient:
69
139
  def __init__(self, *, base_client: AsyncBaseClient):
70
140
  self._base_client = base_client
71
141
 
142
+ async def generate(
143
+ self,
144
+ *,
145
+ assets: params.V1PhotoColorizerGenerateBodyAssets,
146
+ name: typing.Union[
147
+ typing.Optional[str], type_utils.NotGiven
148
+ ] = type_utils.NOT_GIVEN,
149
+ wait_for_completion: bool = True,
150
+ download_outputs: bool = True,
151
+ download_directory: typing.Optional[str] = None,
152
+ request_options: typing.Optional[RequestOptions] = None,
153
+ ):
154
+ """
155
+ Generate colorized photo (alias for create with additional functionality).
156
+
157
+ Colorize image. Each image costs 5 credits.
158
+
159
+ Args:
160
+ name: The name of image. This value is mainly used for your own identification of the image.
161
+ assets: Provide the assets for photo colorization
162
+ wait_for_completion: Whether to wait for the image project to complete
163
+ download_outputs: Whether to download the outputs
164
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
165
+ request_options: Additional options to customize the HTTP request
166
+
167
+ Returns:
168
+ V1ImageProjectsGetResponseWithDownloads: The response from the Photo Colorizer API with the downloaded paths if `download_outputs` is True.
169
+
170
+ Examples:
171
+ ```py
172
+ response = await client.v1.photo_colorizer.generate(
173
+ assets={"image_file_path": "path/to/black_white_photo.jpg"},
174
+ name="Colorized Photo",
175
+ wait_for_completion=True,
176
+ download_outputs=True,
177
+ download_directory="outputs/",
178
+ )
179
+ ```
180
+ """
181
+
182
+ file_client = AsyncFilesClient(base_client=self._base_client)
183
+
184
+ image_file_path = assets["image_file_path"]
185
+ assets["image_file_path"] = await file_client.upload_file(file=image_file_path)
186
+
187
+ create_response = await self.create(
188
+ assets=assets, name=name, request_options=request_options
189
+ )
190
+ logger.info(f"Photo Colorizer response: {create_response}")
191
+
192
+ image_projects_client = AsyncImageProjectsClient(base_client=self._base_client)
193
+ response = await image_projects_client.check_result(
194
+ id=create_response.id,
195
+ wait_for_completion=wait_for_completion,
196
+ download_outputs=download_outputs,
197
+ download_directory=download_directory,
198
+ )
199
+
200
+ return response
201
+
72
202
  async def create(
73
203
  self,
74
204
  *,
@@ -1,3 +1,68 @@
1
+ # v1_text_to_video
2
+
3
+ ## Module Functions
4
+
5
+ <!-- CUSTOM DOCS START -->
6
+
7
+ ### Text To Video Generate Workflow <a name="generate"></a>
8
+
9
+ The workflow performs the following action
10
+
11
+ 1. upload local assets to Magic Hour storage. So you can pass in a local path instead of having to upload files yourself
12
+ 2. trigger a generation
13
+ 3. poll for a completion status. This is configurable
14
+ 4. if success, download the output to local directory
15
+
16
+ > [!TIP]
17
+ > This is the recommended way to use the SDK unless you have specific needs where it is necessary to split up the actions.
18
+
19
+ #### Parameters
20
+
21
+ In Additional to the parameters listed in the `.create` section below, `.generate` introduces 3 new parameters:
22
+
23
+ - `wait_for_completion` (bool, default True): Whether to wait for the project to complete.
24
+ - `download_outputs` (bool, default True): Whether to download the generated files
25
+ - `download_directory` (str, optional): Directory to save downloaded files (defaults to current directory)
26
+
27
+ #### Synchronous Client
28
+
29
+ ```python
30
+ from magic_hour import Client
31
+ from os import getenv
32
+
33
+ client = Client(token=getenv("API_TOKEN"))
34
+ res = client.v1.text_to_video.generate(
35
+ end_seconds=5.0,
36
+ orientation="landscape",
37
+ style={"prompt": "a dog running"},
38
+ name="Text To Video video",
39
+ resolution="720p",
40
+ wait_for_completion=True,
41
+ download_outputs=True,
42
+ download_directory="outputs"
43
+ )
44
+ ```
45
+
46
+ #### Asynchronous Client
47
+
48
+ ```python
49
+ from magic_hour import AsyncClient
50
+ from os import getenv
51
+
52
+ client = AsyncClient(token=getenv("API_TOKEN"))
53
+ res = await client.v1.text_to_video.generate(
54
+ end_seconds=5.0,
55
+ orientation="landscape",
56
+ style={"prompt": "a dog running"},
57
+ name="Text To Video video",
58
+ resolution="720p",
59
+ wait_for_completion=True,
60
+ download_outputs=True,
61
+ download_directory="outputs"
62
+ )
63
+ ```
64
+
65
+ <!-- CUSTOM DOCS END -->
1
66
 
2
67
  ### Text-to-Video <a name="create"></a>
3
68
 
@@ -15,6 +80,8 @@ Get more information about this mode at our [product page](https://magichour.ai/
15
80
  | `end_seconds` | ✓ | The total duration of the output video in seconds. The value must be greater than or equal to 5 seconds and less than or equal to 60 seconds. Note: For 480p resolution, the value must be either 5 or 10. | `5.0` |
16
81
  | `orientation` | ✓ | Determines the orientation of the output video | `"landscape"` |
17
82
  | `style` | ✓ | | `{"prompt": "a dog running"}` |
83
+ | `└─ prompt` | ✓ | The prompt used for the video. | `"a dog running"` |
84
+ | `└─ quality_mode` | ✗ | DEPRECATED: Please use `resolution` field instead. For backward compatibility: * `quick` maps to 720p resolution * `studio` maps to 1080p resolution This field will be removed in a future version. Use the `resolution` field to directly to specify the resolution. | `"quick"` |
18
85
  | `name` | ✗ | The name of video. This value is mainly used for your own identification of the video. | `"Text To Video video"` |
19
86
  | `resolution` | ✗ | Controls the output video resolution. Defaults to `720p` if not specified. 480p and 720p are available on Creator, Pro, or Business tiers. However, 1080p require Pro or Business tier. **Options:** - `480p` - Supports only 5 or 10 second videos. Output: 24fps. Cost: 120 credits per 5 seconds. - `720p` - Supports videos between 5-60 seconds. Output: 30fps. Cost: 300 credits per 5 seconds. - `1080p` - Supports videos between 5-60 seconds. Output: 30fps. Cost: 600 credits per 5 seconds. | `"720p"` |
20
87
 
@@ -59,3 +126,4 @@ res = await client.v1.text_to_video.create(
59
126
 
60
127
  ##### Example
61
128
  `{"credits_charged": 450, "estimated_frame_cost": 450, "id": "cuid-example"}`
129
+