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.
Files changed (264) hide show
  1. magic_hour/README.md +34 -0
  2. magic_hour/__init__.py +1 -1
  3. magic_hour/client.py +8 -17
  4. magic_hour/environment.py +13 -1
  5. magic_hour/helpers/__init__.py +4 -0
  6. magic_hour/helpers/download.py +77 -0
  7. magic_hour/helpers/logger.py +8 -0
  8. magic_hour/resources/v1/README.md +32 -0
  9. magic_hour/resources/v1/ai_clothes_changer/README.md +94 -5
  10. magic_hour/resources/v1/ai_clothes_changer/client.py +161 -16
  11. magic_hour/resources/v1/ai_face_editor/README.md +195 -0
  12. magic_hour/resources/v1/ai_face_editor/__init__.py +4 -0
  13. magic_hour/resources/v1/ai_face_editor/client.py +324 -0
  14. magic_hour/resources/v1/ai_gif_generator/README.md +116 -0
  15. magic_hour/resources/v1/ai_gif_generator/__init__.py +4 -0
  16. magic_hour/resources/v1/ai_gif_generator/client.py +257 -0
  17. magic_hour/resources/v1/ai_headshot_generator/README.md +81 -3
  18. magic_hour/resources/v1/ai_headshot_generator/client.py +167 -18
  19. magic_hour/resources/v1/ai_image_editor/README.md +125 -0
  20. magic_hour/resources/v1/ai_image_editor/__init__.py +4 -0
  21. magic_hour/resources/v1/ai_image_editor/client.py +290 -0
  22. magic_hour/resources/v1/ai_image_generator/README.md +99 -5
  23. magic_hour/resources/v1/ai_image_generator/client.py +170 -24
  24. magic_hour/resources/v1/ai_image_upscaler/README.md +89 -3
  25. magic_hour/resources/v1/ai_image_upscaler/client.py +173 -20
  26. magic_hour/resources/v1/ai_meme_generator/README.md +129 -0
  27. magic_hour/resources/v1/ai_meme_generator/__init__.py +4 -0
  28. magic_hour/resources/v1/ai_meme_generator/client.py +253 -0
  29. magic_hour/resources/v1/ai_photo_editor/README.md +119 -4
  30. magic_hour/resources/v1/ai_photo_editor/client.py +199 -18
  31. magic_hour/resources/v1/ai_qr_code_generator/README.md +84 -3
  32. magic_hour/resources/v1/ai_qr_code_generator/client.py +140 -18
  33. magic_hour/resources/v1/ai_talking_photo/README.md +137 -0
  34. magic_hour/resources/v1/ai_talking_photo/__init__.py +4 -0
  35. magic_hour/resources/v1/ai_talking_photo/client.py +326 -0
  36. magic_hour/resources/v1/ai_voice_cloner/README.md +62 -0
  37. magic_hour/resources/v1/ai_voice_cloner/__init__.py +4 -0
  38. magic_hour/resources/v1/ai_voice_cloner/client.py +272 -0
  39. magic_hour/resources/v1/ai_voice_generator/README.md +112 -0
  40. magic_hour/resources/v1/ai_voice_generator/__init__.py +4 -0
  41. magic_hour/resources/v1/ai_voice_generator/client.py +241 -0
  42. magic_hour/resources/v1/animation/README.md +128 -6
  43. magic_hour/resources/v1/animation/client.py +247 -22
  44. magic_hour/resources/v1/audio_projects/README.md +135 -0
  45. magic_hour/resources/v1/audio_projects/__init__.py +12 -0
  46. magic_hour/resources/v1/audio_projects/client.py +310 -0
  47. magic_hour/resources/v1/audio_projects/client_test.py +520 -0
  48. magic_hour/resources/v1/auto_subtitle_generator/README.md +128 -0
  49. magic_hour/resources/v1/auto_subtitle_generator/__init__.py +4 -0
  50. magic_hour/resources/v1/auto_subtitle_generator/client.py +346 -0
  51. magic_hour/resources/v1/client.py +75 -1
  52. magic_hour/resources/v1/face_detection/README.md +157 -0
  53. magic_hour/resources/v1/face_detection/__init__.py +12 -0
  54. magic_hour/resources/v1/face_detection/client.py +380 -0
  55. magic_hour/resources/v1/face_swap/README.md +137 -9
  56. magic_hour/resources/v1/face_swap/client.py +329 -38
  57. magic_hour/resources/v1/face_swap_photo/README.md +118 -3
  58. magic_hour/resources/v1/face_swap_photo/client.py +199 -14
  59. magic_hour/resources/v1/files/README.md +39 -0
  60. magic_hour/resources/v1/files/client.py +351 -1
  61. magic_hour/resources/v1/files/client_test.py +414 -0
  62. magic_hour/resources/v1/files/upload_urls/README.md +38 -17
  63. magic_hour/resources/v1/files/upload_urls/client.py +38 -34
  64. magic_hour/resources/v1/image_background_remover/README.md +96 -5
  65. magic_hour/resources/v1/image_background_remover/client.py +151 -16
  66. magic_hour/resources/v1/image_projects/README.md +82 -10
  67. magic_hour/resources/v1/image_projects/__init__.py +10 -2
  68. magic_hour/resources/v1/image_projects/client.py +154 -16
  69. magic_hour/resources/v1/image_projects/client_test.py +527 -0
  70. magic_hour/resources/v1/image_to_video/README.md +96 -11
  71. magic_hour/resources/v1/image_to_video/client.py +282 -38
  72. magic_hour/resources/v1/lip_sync/README.md +112 -9
  73. magic_hour/resources/v1/lip_sync/client.py +288 -34
  74. magic_hour/resources/v1/photo_colorizer/README.md +107 -0
  75. magic_hour/resources/v1/photo_colorizer/__init__.py +4 -0
  76. magic_hour/resources/v1/photo_colorizer/client.py +248 -0
  77. magic_hour/resources/v1/text_to_video/README.md +96 -7
  78. magic_hour/resources/v1/text_to_video/client.py +204 -18
  79. magic_hour/resources/v1/video_projects/README.md +81 -9
  80. magic_hour/resources/v1/video_projects/__init__.py +10 -2
  81. magic_hour/resources/v1/video_projects/client.py +151 -14
  82. magic_hour/resources/v1/video_projects/client_test.py +527 -0
  83. magic_hour/resources/v1/video_to_video/README.md +119 -15
  84. magic_hour/resources/v1/video_to_video/client.py +299 -46
  85. magic_hour/types/models/__init__.py +92 -56
  86. magic_hour/types/models/v1_ai_clothes_changer_create_response.py +33 -0
  87. magic_hour/types/models/v1_ai_face_editor_create_response.py +33 -0
  88. magic_hour/types/models/v1_ai_gif_generator_create_response.py +33 -0
  89. magic_hour/types/models/v1_ai_headshot_generator_create_response.py +33 -0
  90. magic_hour/types/models/v1_ai_image_editor_create_response.py +33 -0
  91. magic_hour/types/models/v1_ai_image_generator_create_response.py +33 -0
  92. magic_hour/types/models/v1_ai_image_upscaler_create_response.py +33 -0
  93. magic_hour/types/models/v1_ai_meme_generator_create_response.py +33 -0
  94. magic_hour/types/models/v1_ai_photo_editor_create_response.py +33 -0
  95. magic_hour/types/models/v1_ai_qr_code_generator_create_response.py +33 -0
  96. magic_hour/types/models/v1_ai_talking_photo_create_response.py +35 -0
  97. magic_hour/types/models/v1_ai_voice_cloner_create_response.py +27 -0
  98. magic_hour/types/models/v1_ai_voice_generator_create_response.py +27 -0
  99. magic_hour/types/models/v1_animation_create_response.py +35 -0
  100. magic_hour/types/models/v1_audio_projects_get_response.py +72 -0
  101. magic_hour/types/models/v1_audio_projects_get_response_downloads_item.py +19 -0
  102. magic_hour/types/models/{get_v1_image_projects_id_response_error.py → v1_audio_projects_get_response_error.py} +2 -2
  103. magic_hour/types/models/v1_auto_subtitle_generator_create_response.py +35 -0
  104. magic_hour/types/models/v1_face_detection_create_response.py +25 -0
  105. magic_hour/types/models/v1_face_detection_get_response.py +45 -0
  106. magic_hour/types/models/v1_face_detection_get_response_faces_item.py +25 -0
  107. magic_hour/types/models/v1_face_swap_create_response.py +35 -0
  108. magic_hour/types/models/v1_face_swap_photo_create_response.py +33 -0
  109. magic_hour/types/models/v1_files_upload_urls_create_response.py +24 -0
  110. magic_hour/types/models/{post_v1_files_upload_urls_response_items_item.py → v1_files_upload_urls_create_response_items_item.py} +2 -2
  111. magic_hour/types/models/v1_image_background_remover_create_response.py +33 -0
  112. magic_hour/types/models/{get_v1_image_projects_id_response.py → v1_image_projects_get_response.py} +20 -18
  113. magic_hour/types/models/{get_v1_video_projects_id_response_downloads_item.py → v1_image_projects_get_response_downloads_item.py} +1 -1
  114. magic_hour/types/models/{get_v1_video_projects_id_response_error.py → v1_image_projects_get_response_error.py} +2 -2
  115. magic_hour/types/models/v1_image_to_video_create_response.py +35 -0
  116. magic_hour/types/models/v1_lip_sync_create_response.py +35 -0
  117. magic_hour/types/models/v1_photo_colorizer_create_response.py +33 -0
  118. magic_hour/types/models/v1_text_to_video_create_response.py +35 -0
  119. magic_hour/types/models/{get_v1_video_projects_id_response.py → v1_video_projects_get_response.py} +26 -23
  120. magic_hour/types/models/{get_v1_video_projects_id_response_download.py → v1_video_projects_get_response_download.py} +1 -1
  121. magic_hour/types/models/{get_v1_image_projects_id_response_downloads_item.py → v1_video_projects_get_response_downloads_item.py} +1 -1
  122. magic_hour/types/models/v1_video_projects_get_response_error.py +25 -0
  123. magic_hour/types/models/v1_video_to_video_create_response.py +35 -0
  124. magic_hour/types/params/__init__.py +422 -176
  125. magic_hour/types/params/v1_ai_clothes_changer_create_body.py +40 -0
  126. magic_hour/types/params/v1_ai_clothes_changer_create_body_assets.py +58 -0
  127. magic_hour/types/params/v1_ai_clothes_changer_generate_body_assets.py +33 -0
  128. magic_hour/types/params/v1_ai_face_editor_create_body.py +52 -0
  129. magic_hour/types/params/v1_ai_face_editor_create_body_assets.py +33 -0
  130. magic_hour/types/params/v1_ai_face_editor_create_body_style.py +137 -0
  131. magic_hour/types/params/v1_ai_face_editor_generate_body_assets.py +17 -0
  132. magic_hour/types/params/v1_ai_gif_generator_create_body.py +47 -0
  133. magic_hour/types/params/{post_v1_ai_image_generator_body_style.py → v1_ai_gif_generator_create_body_style.py} +5 -5
  134. magic_hour/types/params/v1_ai_headshot_generator_create_body.py +49 -0
  135. magic_hour/types/params/v1_ai_headshot_generator_create_body_assets.py +33 -0
  136. magic_hour/types/params/v1_ai_headshot_generator_create_body_style.py +27 -0
  137. magic_hour/types/params/v1_ai_headshot_generator_generate_body_assets.py +17 -0
  138. magic_hour/types/params/v1_ai_image_editor_create_body.py +49 -0
  139. magic_hour/types/params/v1_ai_image_editor_create_body_assets.py +47 -0
  140. magic_hour/types/params/v1_ai_image_editor_create_body_style.py +41 -0
  141. magic_hour/types/params/v1_ai_image_editor_generate_body_assets.py +28 -0
  142. magic_hour/types/params/{post_v1_ai_image_generator_body.py → v1_ai_image_generator_create_body.py} +17 -11
  143. magic_hour/types/params/v1_ai_image_generator_create_body_style.py +127 -0
  144. magic_hour/types/params/v1_ai_image_upscaler_create_body.py +59 -0
  145. magic_hour/types/params/v1_ai_image_upscaler_create_body_assets.py +33 -0
  146. magic_hour/types/params/{post_v1_ai_image_upscaler_body_style.py → v1_ai_image_upscaler_create_body_style.py} +4 -4
  147. magic_hour/types/params/v1_ai_image_upscaler_generate_body_assets.py +17 -0
  148. magic_hour/types/params/v1_ai_meme_generator_create_body.py +37 -0
  149. magic_hour/types/params/v1_ai_meme_generator_create_body_style.py +73 -0
  150. magic_hour/types/params/{post_v1_ai_photo_editor_body.py → v1_ai_photo_editor_create_body.py} +15 -15
  151. magic_hour/types/params/v1_ai_photo_editor_create_body_assets.py +33 -0
  152. magic_hour/types/params/{post_v1_ai_photo_editor_body_style.py → v1_ai_photo_editor_create_body_style.py} +20 -4
  153. magic_hour/types/params/v1_ai_photo_editor_generate_body_assets.py +17 -0
  154. magic_hour/types/params/v1_ai_qr_code_generator_create_body.py +45 -0
  155. magic_hour/types/params/{post_v1_ai_qr_code_generator_body_style.py → v1_ai_qr_code_generator_create_body_style.py} +4 -4
  156. magic_hour/types/params/v1_ai_talking_photo_create_body.py +68 -0
  157. magic_hour/types/params/v1_ai_talking_photo_create_body_assets.py +46 -0
  158. magic_hour/types/params/v1_ai_talking_photo_create_body_style.py +44 -0
  159. magic_hour/types/params/v1_ai_talking_photo_generate_body_assets.py +26 -0
  160. magic_hour/types/params/v1_ai_voice_cloner_create_body.py +49 -0
  161. magic_hour/types/params/v1_ai_voice_cloner_create_body_assets.py +33 -0
  162. magic_hour/types/params/v1_ai_voice_cloner_create_body_style.py +28 -0
  163. magic_hour/types/params/v1_ai_voice_cloner_generate_body_assets.py +28 -0
  164. magic_hour/types/params/v1_ai_voice_generator_create_body.py +40 -0
  165. magic_hour/types/params/v1_ai_voice_generator_create_body_style.py +440 -0
  166. magic_hour/types/params/{post_v1_animation_body.py → v1_animation_create_body.py} +16 -16
  167. magic_hour/types/params/{post_v1_animation_body_assets.py → v1_animation_create_body_assets.py} +15 -5
  168. magic_hour/types/params/{post_v1_animation_body_style.py → v1_animation_create_body_style.py} +13 -10
  169. magic_hour/types/params/v1_animation_generate_body_assets.py +39 -0
  170. magic_hour/types/params/v1_auto_subtitle_generator_create_body.py +78 -0
  171. magic_hour/types/params/v1_auto_subtitle_generator_create_body_assets.py +33 -0
  172. magic_hour/types/params/v1_auto_subtitle_generator_create_body_style.py +56 -0
  173. magic_hour/types/params/v1_auto_subtitle_generator_create_body_style_custom_config.py +86 -0
  174. magic_hour/types/params/v1_auto_subtitle_generator_generate_body_assets.py +17 -0
  175. magic_hour/types/params/v1_face_detection_create_body.py +44 -0
  176. magic_hour/types/params/v1_face_detection_create_body_assets.py +33 -0
  177. magic_hour/types/params/v1_face_detection_generate_body_assets.py +17 -0
  178. magic_hour/types/params/v1_face_swap_create_body.py +92 -0
  179. magic_hour/types/params/v1_face_swap_create_body_assets.py +91 -0
  180. magic_hour/types/params/v1_face_swap_create_body_assets_face_mappings_item.py +44 -0
  181. magic_hour/types/params/v1_face_swap_create_body_style.py +33 -0
  182. magic_hour/types/params/v1_face_swap_generate_body_assets.py +56 -0
  183. magic_hour/types/params/v1_face_swap_generate_body_assets_face_mappings_item.py +25 -0
  184. magic_hour/types/params/v1_face_swap_photo_create_body.py +40 -0
  185. magic_hour/types/params/v1_face_swap_photo_create_body_assets.py +76 -0
  186. magic_hour/types/params/v1_face_swap_photo_create_body_assets_face_mappings_item.py +44 -0
  187. magic_hour/types/params/v1_face_swap_photo_generate_body_assets.py +47 -0
  188. magic_hour/types/params/v1_face_swap_photo_generate_body_assets_face_mappings_item.py +25 -0
  189. magic_hour/types/params/v1_files_upload_urls_create_body.py +36 -0
  190. magic_hour/types/params/v1_files_upload_urls_create_body_items_item.py +38 -0
  191. magic_hour/types/params/v1_image_background_remover_create_body.py +40 -0
  192. magic_hour/types/params/v1_image_background_remover_create_body_assets.py +49 -0
  193. magic_hour/types/params/v1_image_background_remover_generate_body_assets.py +27 -0
  194. magic_hour/types/params/v1_image_to_video_create_body.py +101 -0
  195. magic_hour/types/params/v1_image_to_video_create_body_assets.py +33 -0
  196. magic_hour/types/params/v1_image_to_video_create_body_style.py +53 -0
  197. magic_hour/types/params/v1_image_to_video_generate_body_assets.py +17 -0
  198. magic_hour/types/params/v1_lip_sync_create_body.py +100 -0
  199. magic_hour/types/params/{post_v1_lip_sync_body_assets.py → v1_lip_sync_create_body_assets.py} +15 -5
  200. magic_hour/types/params/v1_lip_sync_create_body_style.py +37 -0
  201. magic_hour/types/params/v1_lip_sync_generate_body_assets.py +36 -0
  202. magic_hour/types/params/v1_photo_colorizer_create_body.py +40 -0
  203. magic_hour/types/params/v1_photo_colorizer_create_body_assets.py +33 -0
  204. magic_hour/types/params/v1_photo_colorizer_generate_body_assets.py +17 -0
  205. magic_hour/types/params/v1_text_to_video_create_body.py +78 -0
  206. magic_hour/types/params/v1_text_to_video_create_body_style.py +43 -0
  207. magic_hour/types/params/v1_video_to_video_create_body.py +101 -0
  208. magic_hour/types/params/{post_v1_video_to_video_body_assets.py → v1_video_to_video_create_body_assets.py} +9 -4
  209. magic_hour/types/params/{post_v1_video_to_video_body_style.py → v1_video_to_video_create_body_style.py} +68 -26
  210. magic_hour/types/params/v1_video_to_video_generate_body_assets.py +27 -0
  211. magic_hour-0.44.0.dist-info/METADATA +328 -0
  212. magic_hour-0.44.0.dist-info/RECORD +231 -0
  213. magic_hour/core/__init__.py +0 -52
  214. magic_hour/core/api_error.py +0 -56
  215. magic_hour/core/auth.py +0 -314
  216. magic_hour/core/base_client.py +0 -618
  217. magic_hour/core/binary_response.py +0 -23
  218. magic_hour/core/query.py +0 -106
  219. magic_hour/core/request.py +0 -156
  220. magic_hour/core/response.py +0 -293
  221. magic_hour/core/type_utils.py +0 -28
  222. magic_hour/core/utils.py +0 -55
  223. magic_hour/types/models/post_v1_ai_clothes_changer_response.py +0 -25
  224. magic_hour/types/models/post_v1_ai_headshot_generator_response.py +0 -25
  225. magic_hour/types/models/post_v1_ai_image_generator_response.py +0 -25
  226. magic_hour/types/models/post_v1_ai_image_upscaler_response.py +0 -25
  227. magic_hour/types/models/post_v1_ai_photo_editor_response.py +0 -25
  228. magic_hour/types/models/post_v1_ai_qr_code_generator_response.py +0 -25
  229. magic_hour/types/models/post_v1_animation_response.py +0 -25
  230. magic_hour/types/models/post_v1_face_swap_photo_response.py +0 -25
  231. magic_hour/types/models/post_v1_face_swap_response.py +0 -25
  232. magic_hour/types/models/post_v1_files_upload_urls_response.py +0 -21
  233. magic_hour/types/models/post_v1_image_background_remover_response.py +0 -25
  234. magic_hour/types/models/post_v1_image_to_video_response.py +0 -25
  235. magic_hour/types/models/post_v1_lip_sync_response.py +0 -25
  236. magic_hour/types/models/post_v1_text_to_video_response.py +0 -25
  237. magic_hour/types/models/post_v1_video_to_video_response.py +0 -25
  238. magic_hour/types/params/post_v1_ai_clothes_changer_body.py +0 -40
  239. magic_hour/types/params/post_v1_ai_clothes_changer_body_assets.py +0 -45
  240. magic_hour/types/params/post_v1_ai_headshot_generator_body.py +0 -40
  241. magic_hour/types/params/post_v1_ai_headshot_generator_body_assets.py +0 -28
  242. magic_hour/types/params/post_v1_ai_image_upscaler_body.py +0 -57
  243. magic_hour/types/params/post_v1_ai_image_upscaler_body_assets.py +0 -28
  244. magic_hour/types/params/post_v1_ai_photo_editor_body_assets.py +0 -28
  245. magic_hour/types/params/post_v1_ai_qr_code_generator_body.py +0 -45
  246. magic_hour/types/params/post_v1_face_swap_body.py +0 -72
  247. magic_hour/types/params/post_v1_face_swap_body_assets.py +0 -52
  248. magic_hour/types/params/post_v1_face_swap_photo_body.py +0 -40
  249. magic_hour/types/params/post_v1_face_swap_photo_body_assets.py +0 -36
  250. magic_hour/types/params/post_v1_files_upload_urls_body.py +0 -31
  251. magic_hour/types/params/post_v1_files_upload_urls_body_items_item.py +0 -38
  252. magic_hour/types/params/post_v1_image_background_remover_body.py +0 -40
  253. magic_hour/types/params/post_v1_image_background_remover_body_assets.py +0 -28
  254. magic_hour/types/params/post_v1_image_to_video_body.py +0 -73
  255. magic_hour/types/params/post_v1_image_to_video_body_assets.py +0 -28
  256. magic_hour/types/params/post_v1_image_to_video_body_style.py +0 -37
  257. magic_hour/types/params/post_v1_lip_sync_body.py +0 -80
  258. magic_hour/types/params/post_v1_text_to_video_body.py +0 -57
  259. magic_hour/types/params/post_v1_text_to_video_body_style.py +0 -28
  260. magic_hour/types/params/post_v1_video_to_video_body.py +0 -93
  261. magic_hour-0.9.5.dist-info/METADATA +0 -133
  262. magic_hour-0.9.5.dist-info/RECORD +0 -132
  263. {magic_hour-0.9.5.dist-info → magic_hour-0.44.0.dist-info}/LICENSE +0 -0
  264. {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
- ### create <a name="create"></a>
3
- Generate asset upload urls
3
+ ## Module Functions
4
4
 
5
- Create a list of urls used to upload the assets needed to generate a video. Each video type has their own requirements on what assets are required. Please refer to the specific mode API for more details. The response array will be in the same order as the request body.
5
+ ### Generate asset upload urls <a name="create"></a>
6
6
 
7
- Below is the list of valid extensions for each asset type:
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, mpeg, wav, aac, aiff, flac
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: `.gif` is supported for face swap API `video_file_path` field.
18
+ > Note: `gif` is only supported for face swap API `video_file_path` field.
14
19
 
15
- After receiving the upload url, you can upload the file by sending a PUT request with the header `'Content-Type: application/octet-stream'`.
20
+ Once you receive an upload URL, send a `PUT` request to upload the file directly.
16
21
 
17
- For example using curl
22
+ Example:
18
23
 
19
24
  ```
20
- curl -X PUT -H 'Content-Type: application/octet-stream' \
21
- --data '@/path/to/file/video.mp4' \
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", "type_field": "video"},
38
- {"extension": "mp3", "type_field": "audio"},
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", "type_field": "video"},
53
- {"extension": "mp3", "type_field": "audio"},
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.core import (
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.PostV1FilesUploadUrlsBodyItemsItem],
20
+ items: typing.List[params.V1FilesUploadUrlsCreateBodyItemsItem],
21
21
  request_options: typing.Optional[RequestOptions] = None,
22
- ) -> models.PostV1FilesUploadUrlsResponse:
22
+ ) -> models.V1FilesUploadUrlsCreateResponse:
23
23
  """
24
24
  Generate asset upload urls
25
25
 
26
- Create a list of urls used to upload the assets needed to generate a video. Each video type has their own requirements on what assets are required. Please refer to the specific mode API for more details. The response array will be in the same order as the request body.
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
- Below is the list of valid extensions for each asset type:
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, mpeg, wav, aac, aiff, flac
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: `.gif` is supported for face swap API `video_file_path` field.
36
+ > Note: `gif` is only supported for face swap API `video_file_path` field.
35
37
 
36
- After receiving the upload url, you can upload the file by sending a PUT request with the header `'Content-Type: application/octet-stream'`.
38
+ Once you receive an upload URL, send a `PUT` request to upload the file directly.
37
39
 
38
- For example using curl
40
+ Example:
39
41
 
40
42
  ```
41
- curl -X PUT -H 'Content-Type: application/octet-stream' \
42
- --data '@/path/to/file/video.mp4' \
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: typing.List[PostV1FilesUploadUrlsBodyItemsItem]
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", "type_field": "video"}, {"extension": "mp3", "type_field": "audio"}])
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}, dump_with=params._SerializerPostV1FilesUploadUrlsBody
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.PostV1FilesUploadUrlsResponse,
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.PostV1FilesUploadUrlsBodyItemsItem],
87
+ items: typing.List[params.V1FilesUploadUrlsCreateBodyItemsItem],
86
88
  request_options: typing.Optional[RequestOptions] = None,
87
- ) -> models.PostV1FilesUploadUrlsResponse:
89
+ ) -> models.V1FilesUploadUrlsCreateResponse:
88
90
  """
89
91
  Generate asset upload urls
90
92
 
91
- Create a list of urls used to upload the assets needed to generate a video. Each video type has their own requirements on what assets are required. Please refer to the specific mode API for more details. The response array will be in the same order as the request body.
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
- Below is the list of valid extensions for each asset type:
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, mpeg, wav, aac, aiff, flac
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: `.gif` is supported for face swap API `video_file_path` field.
103
+ > Note: `gif` is only supported for face swap API `video_file_path` field.
100
104
 
101
- After receiving the upload url, you can upload the file by sending a PUT request with the header `'Content-Type: application/octet-stream'`.
105
+ Once you receive an upload URL, send a `PUT` request to upload the file directly.
102
106
 
103
- For example using curl
107
+ Example:
104
108
 
105
109
  ```
106
- curl -X PUT -H 'Content-Type: application/octet-stream' \
107
- --data '@/path/to/file/video.mp4' \
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: typing.List[PostV1FilesUploadUrlsBodyItemsItem]
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", "type_field": "video"}, {"extension": "mp3", "type_field": "audio"}])
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}, dump_with=params._SerializerPostV1FilesUploadUrlsBody
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.PostV1FilesUploadUrlsResponse,
142
+ cast_to=models.V1FilesUploadUrlsCreateResponse,
139
143
  request_options=request_options or default_request_options(),
140
144
  )