magic_hour 0.9.5__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.
- magic_hour/README.md +34 -0
- magic_hour/__init__.py +1 -1
- magic_hour/client.py +8 -17
- magic_hour/environment.py +13 -1
- magic_hour/helpers/__init__.py +4 -0
- magic_hour/helpers/download.py +77 -0
- magic_hour/helpers/logger.py +8 -0
- magic_hour/resources/v1/README.md +32 -0
- magic_hour/resources/v1/ai_clothes_changer/README.md +94 -5
- magic_hour/resources/v1/ai_clothes_changer/client.py +161 -16
- magic_hour/resources/v1/ai_face_editor/README.md +195 -0
- magic_hour/resources/v1/ai_face_editor/__init__.py +4 -0
- magic_hour/resources/v1/ai_face_editor/client.py +324 -0
- magic_hour/resources/v1/ai_gif_generator/README.md +116 -0
- magic_hour/resources/v1/ai_gif_generator/__init__.py +4 -0
- magic_hour/resources/v1/ai_gif_generator/client.py +257 -0
- magic_hour/resources/v1/ai_headshot_generator/README.md +81 -3
- magic_hour/resources/v1/ai_headshot_generator/client.py +167 -18
- magic_hour/resources/v1/ai_image_editor/README.md +125 -0
- magic_hour/resources/v1/ai_image_editor/__init__.py +4 -0
- magic_hour/resources/v1/ai_image_editor/client.py +290 -0
- magic_hour/resources/v1/ai_image_generator/README.md +99 -5
- magic_hour/resources/v1/ai_image_generator/client.py +170 -24
- magic_hour/resources/v1/ai_image_upscaler/README.md +89 -3
- magic_hour/resources/v1/ai_image_upscaler/client.py +173 -20
- magic_hour/resources/v1/ai_meme_generator/README.md +129 -0
- magic_hour/resources/v1/ai_meme_generator/__init__.py +4 -0
- magic_hour/resources/v1/ai_meme_generator/client.py +253 -0
- magic_hour/resources/v1/ai_photo_editor/README.md +119 -4
- magic_hour/resources/v1/ai_photo_editor/client.py +199 -18
- magic_hour/resources/v1/ai_qr_code_generator/README.md +84 -3
- magic_hour/resources/v1/ai_qr_code_generator/client.py +140 -18
- magic_hour/resources/v1/ai_talking_photo/README.md +137 -0
- magic_hour/resources/v1/ai_talking_photo/__init__.py +4 -0
- magic_hour/resources/v1/ai_talking_photo/client.py +326 -0
- magic_hour/resources/v1/ai_voice_cloner/README.md +62 -0
- magic_hour/resources/v1/ai_voice_cloner/__init__.py +4 -0
- magic_hour/resources/v1/ai_voice_cloner/client.py +272 -0
- magic_hour/resources/v1/ai_voice_generator/README.md +112 -0
- magic_hour/resources/v1/ai_voice_generator/__init__.py +4 -0
- magic_hour/resources/v1/ai_voice_generator/client.py +241 -0
- magic_hour/resources/v1/animation/README.md +128 -6
- magic_hour/resources/v1/animation/client.py +247 -22
- magic_hour/resources/v1/audio_projects/README.md +135 -0
- magic_hour/resources/v1/audio_projects/__init__.py +12 -0
- magic_hour/resources/v1/audio_projects/client.py +310 -0
- magic_hour/resources/v1/audio_projects/client_test.py +520 -0
- magic_hour/resources/v1/auto_subtitle_generator/README.md +128 -0
- magic_hour/resources/v1/auto_subtitle_generator/__init__.py +4 -0
- magic_hour/resources/v1/auto_subtitle_generator/client.py +346 -0
- magic_hour/resources/v1/client.py +75 -1
- magic_hour/resources/v1/face_detection/README.md +157 -0
- magic_hour/resources/v1/face_detection/__init__.py +12 -0
- magic_hour/resources/v1/face_detection/client.py +380 -0
- magic_hour/resources/v1/face_swap/README.md +137 -9
- magic_hour/resources/v1/face_swap/client.py +329 -38
- magic_hour/resources/v1/face_swap_photo/README.md +118 -3
- magic_hour/resources/v1/face_swap_photo/client.py +199 -14
- magic_hour/resources/v1/files/README.md +39 -0
- magic_hour/resources/v1/files/client.py +351 -1
- magic_hour/resources/v1/files/client_test.py +414 -0
- magic_hour/resources/v1/files/upload_urls/README.md +38 -17
- magic_hour/resources/v1/files/upload_urls/client.py +38 -34
- magic_hour/resources/v1/image_background_remover/README.md +96 -5
- magic_hour/resources/v1/image_background_remover/client.py +151 -16
- magic_hour/resources/v1/image_projects/README.md +82 -10
- magic_hour/resources/v1/image_projects/__init__.py +10 -2
- magic_hour/resources/v1/image_projects/client.py +154 -16
- magic_hour/resources/v1/image_projects/client_test.py +527 -0
- magic_hour/resources/v1/image_to_video/README.md +96 -11
- magic_hour/resources/v1/image_to_video/client.py +282 -38
- magic_hour/resources/v1/lip_sync/README.md +112 -9
- magic_hour/resources/v1/lip_sync/client.py +288 -34
- magic_hour/resources/v1/photo_colorizer/README.md +107 -0
- magic_hour/resources/v1/photo_colorizer/__init__.py +4 -0
- magic_hour/resources/v1/photo_colorizer/client.py +248 -0
- magic_hour/resources/v1/text_to_video/README.md +96 -7
- magic_hour/resources/v1/text_to_video/client.py +204 -18
- magic_hour/resources/v1/video_projects/README.md +81 -9
- magic_hour/resources/v1/video_projects/__init__.py +10 -2
- magic_hour/resources/v1/video_projects/client.py +151 -14
- magic_hour/resources/v1/video_projects/client_test.py +527 -0
- magic_hour/resources/v1/video_to_video/README.md +119 -15
- magic_hour/resources/v1/video_to_video/client.py +299 -46
- magic_hour/types/models/__init__.py +92 -56
- magic_hour/types/models/v1_ai_clothes_changer_create_response.py +33 -0
- magic_hour/types/models/v1_ai_face_editor_create_response.py +33 -0
- magic_hour/types/models/v1_ai_gif_generator_create_response.py +33 -0
- magic_hour/types/models/v1_ai_headshot_generator_create_response.py +33 -0
- magic_hour/types/models/v1_ai_image_editor_create_response.py +33 -0
- magic_hour/types/models/v1_ai_image_generator_create_response.py +33 -0
- magic_hour/types/models/v1_ai_image_upscaler_create_response.py +33 -0
- magic_hour/types/models/v1_ai_meme_generator_create_response.py +33 -0
- magic_hour/types/models/v1_ai_photo_editor_create_response.py +33 -0
- magic_hour/types/models/v1_ai_qr_code_generator_create_response.py +33 -0
- magic_hour/types/models/v1_ai_talking_photo_create_response.py +35 -0
- magic_hour/types/models/v1_ai_voice_cloner_create_response.py +27 -0
- magic_hour/types/models/v1_ai_voice_generator_create_response.py +27 -0
- magic_hour/types/models/v1_animation_create_response.py +35 -0
- magic_hour/types/models/v1_audio_projects_get_response.py +72 -0
- magic_hour/types/models/v1_audio_projects_get_response_downloads_item.py +19 -0
- magic_hour/types/models/{get_v1_image_projects_id_response_error.py → v1_audio_projects_get_response_error.py} +2 -2
- magic_hour/types/models/v1_auto_subtitle_generator_create_response.py +35 -0
- magic_hour/types/models/v1_face_detection_create_response.py +25 -0
- magic_hour/types/models/v1_face_detection_get_response.py +45 -0
- magic_hour/types/models/v1_face_detection_get_response_faces_item.py +25 -0
- magic_hour/types/models/v1_face_swap_create_response.py +35 -0
- magic_hour/types/models/v1_face_swap_photo_create_response.py +33 -0
- magic_hour/types/models/v1_files_upload_urls_create_response.py +24 -0
- magic_hour/types/models/{post_v1_files_upload_urls_response_items_item.py → v1_files_upload_urls_create_response_items_item.py} +2 -2
- magic_hour/types/models/v1_image_background_remover_create_response.py +33 -0
- magic_hour/types/models/{get_v1_image_projects_id_response.py → v1_image_projects_get_response.py} +20 -18
- magic_hour/types/models/{get_v1_video_projects_id_response_downloads_item.py → v1_image_projects_get_response_downloads_item.py} +1 -1
- magic_hour/types/models/{get_v1_video_projects_id_response_error.py → v1_image_projects_get_response_error.py} +2 -2
- magic_hour/types/models/v1_image_to_video_create_response.py +35 -0
- magic_hour/types/models/v1_lip_sync_create_response.py +35 -0
- magic_hour/types/models/v1_photo_colorizer_create_response.py +33 -0
- magic_hour/types/models/v1_text_to_video_create_response.py +35 -0
- magic_hour/types/models/{get_v1_video_projects_id_response.py → v1_video_projects_get_response.py} +26 -23
- magic_hour/types/models/{get_v1_video_projects_id_response_download.py → v1_video_projects_get_response_download.py} +1 -1
- magic_hour/types/models/{get_v1_image_projects_id_response_downloads_item.py → v1_video_projects_get_response_downloads_item.py} +1 -1
- magic_hour/types/models/v1_video_projects_get_response_error.py +25 -0
- magic_hour/types/models/v1_video_to_video_create_response.py +35 -0
- magic_hour/types/params/__init__.py +422 -176
- magic_hour/types/params/v1_ai_clothes_changer_create_body.py +40 -0
- magic_hour/types/params/v1_ai_clothes_changer_create_body_assets.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_create_body.py +52 -0
- magic_hour/types/params/v1_ai_face_editor_create_body_assets.py +33 -0
- magic_hour/types/params/v1_ai_face_editor_create_body_style.py +137 -0
- magic_hour/types/params/v1_ai_face_editor_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_gif_generator_create_body.py +47 -0
- magic_hour/types/params/{post_v1_ai_image_generator_body_style.py → v1_ai_gif_generator_create_body_style.py} +5 -5
- magic_hour/types/params/v1_ai_headshot_generator_create_body.py +49 -0
- magic_hour/types/params/v1_ai_headshot_generator_create_body_assets.py +33 -0
- magic_hour/types/params/v1_ai_headshot_generator_create_body_style.py +27 -0
- magic_hour/types/params/v1_ai_headshot_generator_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_image_editor_create_body.py +49 -0
- magic_hour/types/params/v1_ai_image_editor_create_body_assets.py +47 -0
- magic_hour/types/params/v1_ai_image_editor_create_body_style.py +41 -0
- magic_hour/types/params/v1_ai_image_editor_generate_body_assets.py +28 -0
- magic_hour/types/params/{post_v1_ai_image_generator_body.py → v1_ai_image_generator_create_body.py} +17 -11
- magic_hour/types/params/v1_ai_image_generator_create_body_style.py +127 -0
- magic_hour/types/params/v1_ai_image_upscaler_create_body.py +59 -0
- magic_hour/types/params/v1_ai_image_upscaler_create_body_assets.py +33 -0
- magic_hour/types/params/{post_v1_ai_image_upscaler_body_style.py → v1_ai_image_upscaler_create_body_style.py} +4 -4
- magic_hour/types/params/v1_ai_image_upscaler_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_meme_generator_create_body.py +37 -0
- magic_hour/types/params/v1_ai_meme_generator_create_body_style.py +73 -0
- magic_hour/types/params/{post_v1_ai_photo_editor_body.py → v1_ai_photo_editor_create_body.py} +15 -15
- magic_hour/types/params/v1_ai_photo_editor_create_body_assets.py +33 -0
- magic_hour/types/params/{post_v1_ai_photo_editor_body_style.py → v1_ai_photo_editor_create_body_style.py} +20 -4
- magic_hour/types/params/v1_ai_photo_editor_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_ai_qr_code_generator_create_body.py +45 -0
- magic_hour/types/params/{post_v1_ai_qr_code_generator_body_style.py → v1_ai_qr_code_generator_create_body_style.py} +4 -4
- magic_hour/types/params/v1_ai_talking_photo_create_body.py +68 -0
- magic_hour/types/params/v1_ai_talking_photo_create_body_assets.py +46 -0
- magic_hour/types/params/v1_ai_talking_photo_create_body_style.py +44 -0
- magic_hour/types/params/v1_ai_talking_photo_generate_body_assets.py +26 -0
- magic_hour/types/params/v1_ai_voice_cloner_create_body.py +49 -0
- magic_hour/types/params/v1_ai_voice_cloner_create_body_assets.py +33 -0
- magic_hour/types/params/v1_ai_voice_cloner_create_body_style.py +28 -0
- magic_hour/types/params/v1_ai_voice_cloner_generate_body_assets.py +28 -0
- magic_hour/types/params/v1_ai_voice_generator_create_body.py +40 -0
- magic_hour/types/params/v1_ai_voice_generator_create_body_style.py +440 -0
- magic_hour/types/params/{post_v1_animation_body.py → v1_animation_create_body.py} +16 -16
- magic_hour/types/params/{post_v1_animation_body_assets.py → v1_animation_create_body_assets.py} +15 -5
- magic_hour/types/params/{post_v1_animation_body_style.py → v1_animation_create_body_style.py} +13 -10
- magic_hour/types/params/v1_animation_generate_body_assets.py +39 -0
- magic_hour/types/params/v1_auto_subtitle_generator_create_body.py +78 -0
- magic_hour/types/params/v1_auto_subtitle_generator_create_body_assets.py +33 -0
- magic_hour/types/params/v1_auto_subtitle_generator_create_body_style.py +56 -0
- magic_hour/types/params/v1_auto_subtitle_generator_create_body_style_custom_config.py +86 -0
- magic_hour/types/params/v1_auto_subtitle_generator_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_face_detection_create_body.py +44 -0
- magic_hour/types/params/v1_face_detection_create_body_assets.py +33 -0
- magic_hour/types/params/v1_face_detection_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_face_swap_create_body.py +92 -0
- magic_hour/types/params/v1_face_swap_create_body_assets.py +91 -0
- magic_hour/types/params/v1_face_swap_create_body_assets_face_mappings_item.py +44 -0
- 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_create_body.py +40 -0
- magic_hour/types/params/v1_face_swap_photo_create_body_assets.py +76 -0
- magic_hour/types/params/v1_face_swap_photo_create_body_assets_face_mappings_item.py +44 -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_files_upload_urls_create_body.py +36 -0
- magic_hour/types/params/v1_files_upload_urls_create_body_items_item.py +38 -0
- magic_hour/types/params/v1_image_background_remover_create_body.py +40 -0
- magic_hour/types/params/v1_image_background_remover_create_body_assets.py +49 -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 +101 -0
- magic_hour/types/params/v1_image_to_video_create_body_assets.py +33 -0
- magic_hour/types/params/v1_image_to_video_create_body_style.py +53 -0
- magic_hour/types/params/v1_image_to_video_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_lip_sync_create_body.py +100 -0
- magic_hour/types/params/{post_v1_lip_sync_body_assets.py → v1_lip_sync_create_body_assets.py} +15 -5
- magic_hour/types/params/v1_lip_sync_create_body_style.py +37 -0
- magic_hour/types/params/v1_lip_sync_generate_body_assets.py +36 -0
- magic_hour/types/params/v1_photo_colorizer_create_body.py +40 -0
- magic_hour/types/params/v1_photo_colorizer_create_body_assets.py +33 -0
- magic_hour/types/params/v1_photo_colorizer_generate_body_assets.py +17 -0
- magic_hour/types/params/v1_text_to_video_create_body.py +78 -0
- magic_hour/types/params/v1_text_to_video_create_body_style.py +43 -0
- magic_hour/types/params/v1_video_to_video_create_body.py +101 -0
- magic_hour/types/params/{post_v1_video_to_video_body_assets.py → v1_video_to_video_create_body_assets.py} +9 -4
- magic_hour/types/params/{post_v1_video_to_video_body_style.py → v1_video_to_video_create_body_style.py} +68 -26
- magic_hour/types/params/v1_video_to_video_generate_body_assets.py +27 -0
- magic_hour-0.44.0.dist-info/METADATA +328 -0
- magic_hour-0.44.0.dist-info/RECORD +231 -0
- magic_hour/core/__init__.py +0 -52
- magic_hour/core/api_error.py +0 -56
- magic_hour/core/auth.py +0 -314
- magic_hour/core/base_client.py +0 -618
- magic_hour/core/binary_response.py +0 -23
- magic_hour/core/query.py +0 -106
- magic_hour/core/request.py +0 -156
- magic_hour/core/response.py +0 -293
- magic_hour/core/type_utils.py +0 -28
- magic_hour/core/utils.py +0 -55
- magic_hour/types/models/post_v1_ai_clothes_changer_response.py +0 -25
- magic_hour/types/models/post_v1_ai_headshot_generator_response.py +0 -25
- magic_hour/types/models/post_v1_ai_image_generator_response.py +0 -25
- magic_hour/types/models/post_v1_ai_image_upscaler_response.py +0 -25
- magic_hour/types/models/post_v1_ai_photo_editor_response.py +0 -25
- magic_hour/types/models/post_v1_ai_qr_code_generator_response.py +0 -25
- magic_hour/types/models/post_v1_animation_response.py +0 -25
- magic_hour/types/models/post_v1_face_swap_photo_response.py +0 -25
- magic_hour/types/models/post_v1_face_swap_response.py +0 -25
- magic_hour/types/models/post_v1_files_upload_urls_response.py +0 -21
- magic_hour/types/models/post_v1_image_background_remover_response.py +0 -25
- magic_hour/types/models/post_v1_image_to_video_response.py +0 -25
- magic_hour/types/models/post_v1_lip_sync_response.py +0 -25
- magic_hour/types/models/post_v1_text_to_video_response.py +0 -25
- magic_hour/types/models/post_v1_video_to_video_response.py +0 -25
- magic_hour/types/params/post_v1_ai_clothes_changer_body.py +0 -40
- magic_hour/types/params/post_v1_ai_clothes_changer_body_assets.py +0 -45
- magic_hour/types/params/post_v1_ai_headshot_generator_body.py +0 -40
- magic_hour/types/params/post_v1_ai_headshot_generator_body_assets.py +0 -28
- magic_hour/types/params/post_v1_ai_image_upscaler_body.py +0 -57
- magic_hour/types/params/post_v1_ai_image_upscaler_body_assets.py +0 -28
- magic_hour/types/params/post_v1_ai_photo_editor_body_assets.py +0 -28
- magic_hour/types/params/post_v1_ai_qr_code_generator_body.py +0 -45
- magic_hour/types/params/post_v1_face_swap_body.py +0 -72
- magic_hour/types/params/post_v1_face_swap_body_assets.py +0 -52
- magic_hour/types/params/post_v1_face_swap_photo_body.py +0 -40
- magic_hour/types/params/post_v1_face_swap_photo_body_assets.py +0 -36
- magic_hour/types/params/post_v1_files_upload_urls_body.py +0 -31
- magic_hour/types/params/post_v1_files_upload_urls_body_items_item.py +0 -38
- magic_hour/types/params/post_v1_image_background_remover_body.py +0 -40
- magic_hour/types/params/post_v1_image_background_remover_body_assets.py +0 -28
- magic_hour/types/params/post_v1_image_to_video_body.py +0 -73
- magic_hour/types/params/post_v1_image_to_video_body_assets.py +0 -28
- magic_hour/types/params/post_v1_image_to_video_body_style.py +0 -37
- magic_hour/types/params/post_v1_lip_sync_body.py +0 -80
- magic_hour/types/params/post_v1_text_to_video_body.py +0 -57
- magic_hour/types/params/post_v1_text_to_video_body_style.py +0 -28
- magic_hour/types/params/post_v1_video_to_video_body.py +0 -93
- magic_hour-0.9.5.dist-info/METADATA +0 -133
- magic_hour-0.9.5.dist-info/RECORD +0 -132
- {magic_hour-0.9.5.dist-info → magic_hour-0.44.0.dist-info}/LICENSE +0 -0
- {magic_hour-0.9.5.dist-info → magic_hour-0.44.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import tempfile
|
|
3
|
+
import os
|
|
4
|
+
import io
|
|
5
|
+
import pathlib
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
from magic_hour import AsyncClient, Client
|
|
9
|
+
from magic_hour.environment import Environment
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_upload_file_local():
|
|
13
|
+
data = b"test data"
|
|
14
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
|
|
15
|
+
tmp.write(data)
|
|
16
|
+
tmp_path = tmp.name
|
|
17
|
+
|
|
18
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
19
|
+
|
|
20
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
21
|
+
mock_put.return_value = mock.Mock(
|
|
22
|
+
status_code=200, raise_for_status=lambda: None
|
|
23
|
+
)
|
|
24
|
+
result = client.v1.files.upload_file(tmp_path)
|
|
25
|
+
assert result == "api-assets/id/video.mp4"
|
|
26
|
+
mock_put.assert_called_once_with(url=mock.ANY, content=data)
|
|
27
|
+
|
|
28
|
+
os.remove(tmp_path)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_async_upload_file_local():
|
|
33
|
+
data = b"test data"
|
|
34
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
|
|
35
|
+
tmp.write(data)
|
|
36
|
+
tmp_path = tmp.name
|
|
37
|
+
|
|
38
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
39
|
+
|
|
40
|
+
with mock.patch("httpx.AsyncClient.put", new_callable=mock.AsyncMock) as mock_put:
|
|
41
|
+
mock_put.return_value = mock.Mock(
|
|
42
|
+
status_code=200, raise_for_status=lambda: None
|
|
43
|
+
)
|
|
44
|
+
result = await client.v1.files.upload_file(tmp_path)
|
|
45
|
+
assert result == "api-assets/id/video.mp4"
|
|
46
|
+
mock_put.assert_awaited_once_with(url=mock.ANY, content=data)
|
|
47
|
+
|
|
48
|
+
os.remove(tmp_path)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_upload_file_with_binary_io():
|
|
52
|
+
data = b"test image data"
|
|
53
|
+
file_obj = io.BytesIO(data)
|
|
54
|
+
file_obj.name = "test.png" # Required for extension detection
|
|
55
|
+
|
|
56
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
57
|
+
|
|
58
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
59
|
+
mock_put.return_value = mock.Mock(
|
|
60
|
+
status_code=200, raise_for_status=lambda: None
|
|
61
|
+
)
|
|
62
|
+
result = client.v1.files.upload_file(file_obj)
|
|
63
|
+
assert result == "api-assets/id/video.mp4"
|
|
64
|
+
mock_put.assert_called_once_with(
|
|
65
|
+
url=mock.ANY,
|
|
66
|
+
content=data,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_async_upload_file_with_binary_io():
|
|
72
|
+
data = b"test audio data"
|
|
73
|
+
file_obj = io.BytesIO(data)
|
|
74
|
+
file_obj.name = "test.wav"
|
|
75
|
+
|
|
76
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
77
|
+
|
|
78
|
+
with mock.patch("httpx.AsyncClient.put", new_callable=mock.AsyncMock) as mock_put:
|
|
79
|
+
mock_put.return_value = mock.Mock(
|
|
80
|
+
status_code=200, raise_for_status=lambda: None
|
|
81
|
+
)
|
|
82
|
+
result = await client.v1.files.upload_file(file_obj)
|
|
83
|
+
assert result == "api-assets/id/video.mp4"
|
|
84
|
+
mock_put.assert_awaited_once_with(url=mock.ANY, content=data)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Test pathlib.Path input
|
|
88
|
+
def test_upload_file_with_pathlib_path():
|
|
89
|
+
data = b"test data"
|
|
90
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp:
|
|
91
|
+
tmp.write(data)
|
|
92
|
+
tmp_path = pathlib.Path(tmp.name)
|
|
93
|
+
|
|
94
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
95
|
+
|
|
96
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
97
|
+
mock_put.return_value = mock.Mock(
|
|
98
|
+
status_code=200, raise_for_status=lambda: None
|
|
99
|
+
)
|
|
100
|
+
result = client.v1.files.upload_file(tmp_path)
|
|
101
|
+
assert result == "api-assets/id/video.mp4"
|
|
102
|
+
mock_put.assert_called_once_with(url=mock.ANY, content=data)
|
|
103
|
+
|
|
104
|
+
os.remove(tmp_path)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Test error cases
|
|
108
|
+
def test_upload_file_nonexistent_file():
|
|
109
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
110
|
+
|
|
111
|
+
with pytest.raises(
|
|
112
|
+
FileNotFoundError, match="File not found: /nonexistent/file.mp4"
|
|
113
|
+
):
|
|
114
|
+
client.v1.files.upload_file("/nonexistent/file.mp4")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_upload_file_unsupported_file_type():
|
|
118
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as tmp:
|
|
119
|
+
tmp.write(b"test data")
|
|
120
|
+
tmp_path = tmp.name
|
|
121
|
+
|
|
122
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
123
|
+
|
|
124
|
+
with pytest.raises(ValueError, match="Could not determine file type"):
|
|
125
|
+
client.v1.files.upload_file(tmp_path)
|
|
126
|
+
|
|
127
|
+
os.remove(tmp_path)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_upload_file_binary_io_without_name():
|
|
131
|
+
data = b"test data"
|
|
132
|
+
file_obj = io.BytesIO(data)
|
|
133
|
+
# Intentionally not setting name attribute
|
|
134
|
+
|
|
135
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
136
|
+
|
|
137
|
+
with pytest.raises(
|
|
138
|
+
ValueError, match="File-like object must have a 'name' attribute"
|
|
139
|
+
):
|
|
140
|
+
client.v1.files.upload_file(file_obj)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_upload_file_binary_io_with_non_string_name():
|
|
144
|
+
data = b"test data"
|
|
145
|
+
file_obj = io.BytesIO(data)
|
|
146
|
+
file_obj.name = 123 # Non-string name
|
|
147
|
+
|
|
148
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
149
|
+
|
|
150
|
+
with pytest.raises(
|
|
151
|
+
ValueError, match="File-like object must have a 'name' attribute of type str"
|
|
152
|
+
):
|
|
153
|
+
client.v1.files.upload_file(file_obj)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_upload_file_no_upload_url_returned():
|
|
157
|
+
data = b"test data"
|
|
158
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
|
|
159
|
+
tmp.write(data)
|
|
160
|
+
tmp_path = tmp.name
|
|
161
|
+
|
|
162
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
163
|
+
|
|
164
|
+
# Mock the upload_urls.create to return empty items
|
|
165
|
+
with mock.patch.object(client.v1.files.upload_urls, "create") as mock_create:
|
|
166
|
+
mock_create.return_value = mock.Mock(items=[])
|
|
167
|
+
|
|
168
|
+
with pytest.raises(
|
|
169
|
+
ValueError, match="No upload URL was returned from the server"
|
|
170
|
+
):
|
|
171
|
+
client.v1.files.upload_file(tmp_path)
|
|
172
|
+
|
|
173
|
+
os.remove(tmp_path)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_upload_file_http_error():
|
|
177
|
+
data = b"test data"
|
|
178
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
|
|
179
|
+
tmp.write(data)
|
|
180
|
+
tmp_path = tmp.name
|
|
181
|
+
|
|
182
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
183
|
+
|
|
184
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
185
|
+
# Mock HTTP error response
|
|
186
|
+
mock_response = mock.Mock()
|
|
187
|
+
mock_response.raise_for_status.side_effect = Exception("Upload failed")
|
|
188
|
+
mock_put.return_value = mock_response
|
|
189
|
+
|
|
190
|
+
with pytest.raises(Exception, match="Upload failed"):
|
|
191
|
+
client.v1.files.upload_file(tmp_path)
|
|
192
|
+
|
|
193
|
+
os.remove(tmp_path)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# Test different file types to ensure proper type detection
|
|
197
|
+
def test_upload_different_file_types():
|
|
198
|
+
test_cases = [
|
|
199
|
+
(".mp4", "video"),
|
|
200
|
+
(".mov", "video"),
|
|
201
|
+
(".webm", "video"),
|
|
202
|
+
(".mp3", "audio"),
|
|
203
|
+
(".wav", "audio"),
|
|
204
|
+
(".png", "image"),
|
|
205
|
+
(".jpg", "image"),
|
|
206
|
+
(".jpeg", "image"),
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
210
|
+
|
|
211
|
+
for extension, expected_type in test_cases:
|
|
212
|
+
data = b"test data"
|
|
213
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=extension) as tmp:
|
|
214
|
+
tmp.write(data)
|
|
215
|
+
tmp_path = tmp.name
|
|
216
|
+
|
|
217
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
218
|
+
mock_put.return_value = mock.Mock(
|
|
219
|
+
status_code=200, raise_for_status=lambda: None
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Mock the upload_urls.create to verify correct type is passed
|
|
223
|
+
with mock.patch.object(
|
|
224
|
+
client.v1.files.upload_urls, "create"
|
|
225
|
+
) as mock_create:
|
|
226
|
+
mock_create.return_value = mock.Mock(
|
|
227
|
+
items=[
|
|
228
|
+
mock.Mock(
|
|
229
|
+
upload_url="https://test.com/upload",
|
|
230
|
+
file_path="api-assets/id/video.mp4",
|
|
231
|
+
)
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
result = client.v1.files.upload_file(tmp_path)
|
|
236
|
+
assert result == "api-assets/id/video.mp4"
|
|
237
|
+
|
|
238
|
+
# Verify the correct type and extension were passed
|
|
239
|
+
call_args = mock_create.call_args
|
|
240
|
+
items = call_args.kwargs["items"]
|
|
241
|
+
assert len(items) == 1
|
|
242
|
+
assert items[0]["type_"] == expected_type
|
|
243
|
+
assert items[0]["extension"] == extension[1:] # without the dot
|
|
244
|
+
|
|
245
|
+
os.remove(tmp_path)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# Test URL handling - should skip upload and return URL as is
|
|
249
|
+
def test_upload_file_with_http_url():
|
|
250
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
251
|
+
|
|
252
|
+
http_url = "http://example.com/image.jpg"
|
|
253
|
+
result = client.v1.files.upload_file(http_url)
|
|
254
|
+
|
|
255
|
+
assert result == http_url
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_upload_file_with_https_url():
|
|
259
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
260
|
+
|
|
261
|
+
https_url = "https://example.com/video.mp4"
|
|
262
|
+
result = client.v1.files.upload_file(https_url)
|
|
263
|
+
|
|
264
|
+
assert result == https_url
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@pytest.mark.asyncio
|
|
268
|
+
async def test_async_upload_file_with_http_url():
|
|
269
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
270
|
+
|
|
271
|
+
http_url = "http://example.com/audio.mp3"
|
|
272
|
+
result = await client.v1.files.upload_file(http_url)
|
|
273
|
+
|
|
274
|
+
assert result == http_url
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@pytest.mark.asyncio
|
|
278
|
+
async def test_async_upload_file_with_https_url():
|
|
279
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
280
|
+
|
|
281
|
+
https_url = "https://example.com/document.pdf"
|
|
282
|
+
result = await client.v1.files.upload_file(https_url)
|
|
283
|
+
|
|
284
|
+
assert result == https_url
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Test blob path handling - should skip upload and return blob path as is
|
|
288
|
+
def test_upload_file_with_blob_path():
|
|
289
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
290
|
+
|
|
291
|
+
blob_path = "api-assets/user123/image.jpg"
|
|
292
|
+
result = client.v1.files.upload_file(blob_path)
|
|
293
|
+
|
|
294
|
+
assert result == blob_path
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_upload_file_with_blob_path_different_format():
|
|
298
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
299
|
+
|
|
300
|
+
blob_path = "api-assets/project456/video.mp4"
|
|
301
|
+
result = client.v1.files.upload_file(blob_path)
|
|
302
|
+
|
|
303
|
+
assert result == blob_path
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@pytest.mark.asyncio
|
|
307
|
+
async def test_async_upload_file_with_blob_path():
|
|
308
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
309
|
+
|
|
310
|
+
blob_path = "api-assets/user789/audio.wav"
|
|
311
|
+
result = await client.v1.files.upload_file(blob_path)
|
|
312
|
+
|
|
313
|
+
assert result == blob_path
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@pytest.mark.asyncio
|
|
317
|
+
async def test_async_upload_file_with_blob_path_different_format():
|
|
318
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
319
|
+
|
|
320
|
+
blob_path = "api-assets/session101/photo.png"
|
|
321
|
+
result = await client.v1.files.upload_file(blob_path)
|
|
322
|
+
|
|
323
|
+
assert result == blob_path
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# Test that URL and blob path handling doesn't make HTTP requests
|
|
327
|
+
def test_upload_file_with_url_does_not_make_http_requests():
|
|
328
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
329
|
+
|
|
330
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
331
|
+
with mock.patch.object(client.v1.files.upload_urls, "create") as mock_create:
|
|
332
|
+
result = client.v1.files.upload_file("https://example.com/file.jpg")
|
|
333
|
+
|
|
334
|
+
# Should not call upload_urls.create or make HTTP PUT request
|
|
335
|
+
mock_create.assert_not_called()
|
|
336
|
+
mock_put.assert_not_called()
|
|
337
|
+
|
|
338
|
+
assert result == "https://example.com/file.jpg"
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def test_upload_file_with_blob_path_does_not_make_http_requests():
|
|
342
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
343
|
+
|
|
344
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
345
|
+
with mock.patch.object(client.v1.files.upload_urls, "create") as mock_create:
|
|
346
|
+
result = client.v1.files.upload_file("api-assets/user123/file.mp4")
|
|
347
|
+
|
|
348
|
+
# Should not call upload_urls.create or make HTTP PUT request
|
|
349
|
+
mock_create.assert_not_called()
|
|
350
|
+
mock_put.assert_not_called()
|
|
351
|
+
|
|
352
|
+
assert result == "api-assets/user123/file.mp4"
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@pytest.mark.asyncio
|
|
356
|
+
async def test_async_upload_file_with_url_does_not_make_http_requests():
|
|
357
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
358
|
+
|
|
359
|
+
with mock.patch("httpx.AsyncClient.put", new_callable=mock.AsyncMock) as mock_put:
|
|
360
|
+
with mock.patch.object(
|
|
361
|
+
client.v1.files.upload_urls, "create", new_callable=mock.AsyncMock
|
|
362
|
+
) as mock_create:
|
|
363
|
+
result = await client.v1.files.upload_file("http://example.com/file.mp3")
|
|
364
|
+
|
|
365
|
+
# Should not call upload_urls.create or make HTTP PUT request
|
|
366
|
+
mock_create.assert_not_awaited()
|
|
367
|
+
mock_put.assert_not_awaited()
|
|
368
|
+
|
|
369
|
+
assert result == "http://example.com/file.mp3"
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@pytest.mark.asyncio
|
|
373
|
+
async def test_async_upload_file_with_blob_path_does_not_make_http_requests():
|
|
374
|
+
client = AsyncClient(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
375
|
+
|
|
376
|
+
with mock.patch("httpx.AsyncClient.put", new_callable=mock.AsyncMock) as mock_put:
|
|
377
|
+
with mock.patch.object(
|
|
378
|
+
client.v1.files.upload_urls, "create", new_callable=mock.AsyncMock
|
|
379
|
+
) as mock_create:
|
|
380
|
+
result = await client.v1.files.upload_file("api-assets/user456/file.wav")
|
|
381
|
+
|
|
382
|
+
# Should not call upload_urls.create or make HTTP PUT request
|
|
383
|
+
mock_create.assert_not_awaited()
|
|
384
|
+
mock_put.assert_not_awaited()
|
|
385
|
+
|
|
386
|
+
assert result == "api-assets/user456/file.wav"
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# Test file position preservation for file-like objects
|
|
390
|
+
def test_upload_file_preserves_file_position():
|
|
391
|
+
data = b"test data for position preservation"
|
|
392
|
+
file_obj = io.BytesIO(data)
|
|
393
|
+
file_obj.name = "test.mp4"
|
|
394
|
+
|
|
395
|
+
# Move to middle of file
|
|
396
|
+
file_obj.seek(5)
|
|
397
|
+
original_position = file_obj.tell()
|
|
398
|
+
|
|
399
|
+
client = Client(token="API_TOKEN", environment=Environment.MOCK_SERVER)
|
|
400
|
+
|
|
401
|
+
with mock.patch("httpx.Client.put") as mock_put:
|
|
402
|
+
mock_put.return_value = mock.Mock(
|
|
403
|
+
status_code=200, raise_for_status=lambda: None
|
|
404
|
+
)
|
|
405
|
+
client.v1.files.upload_file(file_obj)
|
|
406
|
+
|
|
407
|
+
# Verify position was restored
|
|
408
|
+
assert file_obj.tell() == original_position
|
|
409
|
+
|
|
410
|
+
# Verify the full content was uploaded (not just from position 5)
|
|
411
|
+
mock_put.assert_called_once_with(
|
|
412
|
+
url=mock.ANY,
|
|
413
|
+
content=data, # Full data, not data[5:]
|
|
414
|
+
)
|
|
@@ -1,30 +1,39 @@
|
|
|
1
|
+
# v1.files.upload_urls
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
Generate asset upload urls
|
|
3
|
+
## Module Functions
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Generate asset upload urls <a name="create"></a>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Generates a list of pre-signed upload URLs for the assets required. This API is only necessary if you want to upload to Magic Hour's storage. Refer to the [Input Files Guide](/integration/input-files) for more details.
|
|
8
|
+
|
|
9
|
+
The response array will match the order of items in the request body.
|
|
10
|
+
|
|
11
|
+
**Valid file extensions per asset type**:
|
|
8
12
|
|
|
9
13
|
- video: mp4, m4v, mov, webm
|
|
10
|
-
- audio: mp3,
|
|
11
|
-
- image: png, jpg, jpeg, webp, avif, jp2, tiff, bmp
|
|
14
|
+
- audio: mp3, wav, aac, flac, webm
|
|
15
|
+
- image: png, jpg, jpeg, heic, webp, avif, jp2, tiff, bmp
|
|
16
|
+
- gif: gif, webp, webm
|
|
12
17
|
|
|
13
|
-
Note:
|
|
18
|
+
> Note: `gif` is only supported for face swap API `video_file_path` field.
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
Once you receive an upload URL, send a `PUT` request to upload the file directly.
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
Example:
|
|
18
23
|
|
|
19
24
|
```
|
|
20
|
-
curl -X PUT
|
|
21
|
-
|
|
22
|
-
https://videos.magichour.ai/api-assets/id/video.mp4?auth-value=1234567890
|
|
25
|
+
curl -X PUT --data '@/path/to/file/video.mp4' \
|
|
26
|
+
https://videos.magichour.ai/api-assets/id/video.mp4?<auth params from the API response>
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
|
|
26
29
|
**API Endpoint**: `POST /v1/files/upload-urls`
|
|
27
30
|
|
|
31
|
+
#### Parameters
|
|
32
|
+
|
|
33
|
+
| Parameter | Required | Description | Example |
|
|
34
|
+
| --------- | :------: | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
35
|
+
| `items` | ✓ | The list of assets to upload. The response array will match the order of items in the request body. | `[{"extension": "mp4", "type_": "video"}, {"extension": "mp3", "type_": "audio"}]` |
|
|
36
|
+
|
|
28
37
|
#### Synchronous Client
|
|
29
38
|
|
|
30
39
|
```python
|
|
@@ -34,8 +43,8 @@ from os import getenv
|
|
|
34
43
|
client = Client(token=getenv("API_TOKEN"))
|
|
35
44
|
res = client.v1.files.upload_urls.create(
|
|
36
45
|
items=[
|
|
37
|
-
{"extension": "mp4", "
|
|
38
|
-
{"extension": "mp3", "
|
|
46
|
+
{"extension": "mp4", "type_": "video"},
|
|
47
|
+
{"extension": "mp3", "type_": "audio"},
|
|
39
48
|
]
|
|
40
49
|
)
|
|
41
50
|
```
|
|
@@ -49,8 +58,20 @@ from os import getenv
|
|
|
49
58
|
client = AsyncClient(token=getenv("API_TOKEN"))
|
|
50
59
|
res = await client.v1.files.upload_urls.create(
|
|
51
60
|
items=[
|
|
52
|
-
{"extension": "mp4", "
|
|
53
|
-
{"extension": "mp3", "
|
|
61
|
+
{"extension": "mp4", "type_": "video"},
|
|
62
|
+
{"extension": "mp3", "type_": "audio"},
|
|
54
63
|
]
|
|
55
64
|
)
|
|
56
65
|
```
|
|
66
|
+
|
|
67
|
+
#### Response
|
|
68
|
+
|
|
69
|
+
##### Type
|
|
70
|
+
|
|
71
|
+
[V1FilesUploadUrlsCreateResponse](/magic_hour/types/models/v1_files_upload_urls_create_response.py)
|
|
72
|
+
|
|
73
|
+
##### Example
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
{"items": [{"expires_at": "2024-07-25T16:56:21.932Z", "file_path": "api-assets/id/video.mp4", "upload_url": "https://videos.magichour.ai/api-assets/id/video.mp4?auth-value=1234567890"}, {"expires_at": "2024-07-25T16:56:21.932Z", "file_path": "api-assets/id/audio.mp3", "upload_url": "https://videos.magichour.ai/api-assets/id/audio.mp3?auth-value=1234567890"}]}
|
|
77
|
+
```
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
|
|
3
|
-
from magic_hour.
|
|
3
|
+
from magic_hour.types import models, params
|
|
4
|
+
from make_api_request import (
|
|
4
5
|
AsyncBaseClient,
|
|
5
6
|
RequestOptions,
|
|
6
7
|
SyncBaseClient,
|
|
7
8
|
default_request_options,
|
|
8
9
|
to_encodable,
|
|
9
10
|
)
|
|
10
|
-
from magic_hour.types import models, params
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class UploadUrlsClient:
|
|
@@ -17,37 +17,38 @@ class UploadUrlsClient:
|
|
|
17
17
|
def create(
|
|
18
18
|
self,
|
|
19
19
|
*,
|
|
20
|
-
items: typing.List[params.
|
|
20
|
+
items: typing.List[params.V1FilesUploadUrlsCreateBodyItemsItem],
|
|
21
21
|
request_options: typing.Optional[RequestOptions] = None,
|
|
22
|
-
) -> models.
|
|
22
|
+
) -> models.V1FilesUploadUrlsCreateResponse:
|
|
23
23
|
"""
|
|
24
24
|
Generate asset upload urls
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Generates a list of pre-signed upload URLs for the assets required. This API is only necessary if you want to upload to Magic Hour's storage. Refer to the [Input Files Guide](/integration/input-files) for more details.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
The response array will match the order of items in the request body.
|
|
29
29
|
|
|
30
|
+
**Valid file extensions per asset type**:
|
|
30
31
|
- video: mp4, m4v, mov, webm
|
|
31
|
-
- audio: mp3,
|
|
32
|
-
- image: png, jpg, jpeg, webp, avif, jp2, tiff, bmp
|
|
32
|
+
- audio: mp3, wav, aac, flac, webm
|
|
33
|
+
- image: png, jpg, jpeg, heic, webp, avif, jp2, tiff, bmp
|
|
34
|
+
- gif: gif, webp, webm
|
|
33
35
|
|
|
34
|
-
Note:
|
|
36
|
+
> Note: `gif` is only supported for face swap API `video_file_path` field.
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
Once you receive an upload URL, send a `PUT` request to upload the file directly.
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
Example:
|
|
39
41
|
|
|
40
42
|
```
|
|
41
|
-
curl -X PUT
|
|
42
|
-
|
|
43
|
-
https://videos.magichour.ai/api-assets/id/video.mp4?auth-value=1234567890
|
|
43
|
+
curl -X PUT --data '@/path/to/file/video.mp4' \
|
|
44
|
+
https://videos.magichour.ai/api-assets/id/video.mp4?<auth params from the API response>
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
POST /v1/files/upload-urls
|
|
48
49
|
|
|
49
50
|
Args:
|
|
50
|
-
items:
|
|
51
|
+
items: The list of assets to upload. The response array will match the order of items in the request body.
|
|
51
52
|
request_options: Additional options to customize the HTTP request
|
|
52
53
|
|
|
53
54
|
Returns:
|
|
@@ -59,18 +60,19 @@ class UploadUrlsClient:
|
|
|
59
60
|
|
|
60
61
|
Examples:
|
|
61
62
|
```py
|
|
62
|
-
client.v1.files.upload_urls.create(items=[{"extension": "mp4", "
|
|
63
|
+
client.v1.files.upload_urls.create(items=[{"extension": "mp4", "type_": "video"}, {"extension": "mp3", "type_": "audio"}])
|
|
63
64
|
```
|
|
64
65
|
"""
|
|
65
66
|
_json = to_encodable(
|
|
66
|
-
item={"items": items},
|
|
67
|
+
item={"items": items},
|
|
68
|
+
dump_with=params._SerializerV1FilesUploadUrlsCreateBody,
|
|
67
69
|
)
|
|
68
70
|
return self._base_client.request(
|
|
69
71
|
method="POST",
|
|
70
72
|
path="/v1/files/upload-urls",
|
|
71
73
|
auth_names=["bearerAuth"],
|
|
72
74
|
json=_json,
|
|
73
|
-
cast_to=models.
|
|
75
|
+
cast_to=models.V1FilesUploadUrlsCreateResponse,
|
|
74
76
|
request_options=request_options or default_request_options(),
|
|
75
77
|
)
|
|
76
78
|
|
|
@@ -82,37 +84,38 @@ class AsyncUploadUrlsClient:
|
|
|
82
84
|
async def create(
|
|
83
85
|
self,
|
|
84
86
|
*,
|
|
85
|
-
items: typing.List[params.
|
|
87
|
+
items: typing.List[params.V1FilesUploadUrlsCreateBodyItemsItem],
|
|
86
88
|
request_options: typing.Optional[RequestOptions] = None,
|
|
87
|
-
) -> models.
|
|
89
|
+
) -> models.V1FilesUploadUrlsCreateResponse:
|
|
88
90
|
"""
|
|
89
91
|
Generate asset upload urls
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
Generates a list of pre-signed upload URLs for the assets required. This API is only necessary if you want to upload to Magic Hour's storage. Refer to the [Input Files Guide](/integration/input-files) for more details.
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
The response array will match the order of items in the request body.
|
|
94
96
|
|
|
97
|
+
**Valid file extensions per asset type**:
|
|
95
98
|
- video: mp4, m4v, mov, webm
|
|
96
|
-
- audio: mp3,
|
|
97
|
-
- image: png, jpg, jpeg, webp, avif, jp2, tiff, bmp
|
|
99
|
+
- audio: mp3, wav, aac, flac, webm
|
|
100
|
+
- image: png, jpg, jpeg, heic, webp, avif, jp2, tiff, bmp
|
|
101
|
+
- gif: gif, webp, webm
|
|
98
102
|
|
|
99
|
-
Note:
|
|
103
|
+
> Note: `gif` is only supported for face swap API `video_file_path` field.
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
Once you receive an upload URL, send a `PUT` request to upload the file directly.
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
Example:
|
|
104
108
|
|
|
105
109
|
```
|
|
106
|
-
curl -X PUT
|
|
107
|
-
|
|
108
|
-
https://videos.magichour.ai/api-assets/id/video.mp4?auth-value=1234567890
|
|
110
|
+
curl -X PUT --data '@/path/to/file/video.mp4' \
|
|
111
|
+
https://videos.magichour.ai/api-assets/id/video.mp4?<auth params from the API response>
|
|
109
112
|
```
|
|
110
113
|
|
|
111
114
|
|
|
112
115
|
POST /v1/files/upload-urls
|
|
113
116
|
|
|
114
117
|
Args:
|
|
115
|
-
items:
|
|
118
|
+
items: The list of assets to upload. The response array will match the order of items in the request body.
|
|
116
119
|
request_options: Additional options to customize the HTTP request
|
|
117
120
|
|
|
118
121
|
Returns:
|
|
@@ -124,17 +127,18 @@ class AsyncUploadUrlsClient:
|
|
|
124
127
|
|
|
125
128
|
Examples:
|
|
126
129
|
```py
|
|
127
|
-
await client.v1.files.upload_urls.create(items=[{"extension": "mp4", "
|
|
130
|
+
await client.v1.files.upload_urls.create(items=[{"extension": "mp4", "type_": "video"}, {"extension": "mp3", "type_": "audio"}])
|
|
128
131
|
```
|
|
129
132
|
"""
|
|
130
133
|
_json = to_encodable(
|
|
131
|
-
item={"items": items},
|
|
134
|
+
item={"items": items},
|
|
135
|
+
dump_with=params._SerializerV1FilesUploadUrlsCreateBody,
|
|
132
136
|
)
|
|
133
137
|
return await self._base_client.request(
|
|
134
138
|
method="POST",
|
|
135
139
|
path="/v1/files/upload-urls",
|
|
136
140
|
auth_names=["bearerAuth"],
|
|
137
141
|
json=_json,
|
|
138
|
-
cast_to=models.
|
|
142
|
+
cast_to=models.V1FilesUploadUrlsCreateResponse,
|
|
139
143
|
request_options=request_options or default_request_options(),
|
|
140
144
|
)
|