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
@@ -1,17 +1,367 @@
1
- from magic_hour.core import AsyncBaseClient, SyncBaseClient
1
+ import httpx
2
+ import io
3
+ import mimetypes
4
+ import os
5
+ import pathlib
6
+ import typing
7
+ import typing_extensions
8
+
9
+ from magic_hour.helpers.logger import get_sdk_logger
2
10
  from magic_hour.resources.v1.files.upload_urls import (
3
11
  AsyncUploadUrlsClient,
4
12
  UploadUrlsClient,
5
13
  )
14
+ from magic_hour.types.params.v1_files_upload_urls_create_body_items_item import (
15
+ V1FilesUploadUrlsCreateBodyItemsItem,
16
+ )
17
+ from make_api_request import AsyncBaseClient, SyncBaseClient
18
+ from pathlib import Path
19
+
20
+
21
+ logger = get_sdk_logger(__name__)
22
+
23
+
24
+ def _get_file_type_and_extension(file_path: str):
25
+ """
26
+ Determine file type and extension from file path.
27
+
28
+ Args:
29
+ file_path: Path to the file
30
+
31
+ Returns:
32
+ Tuple of (file_type, extension) where file_type is one of "video", "audio", or "image"
33
+ and extension is the lowercase file extension without the dot
34
+ """
35
+ ext = Path(file_path).suffix.lower()
36
+ if ext.startswith("."):
37
+ ext = ext[1:] # Remove the leading dot
38
+
39
+ file_type: typing.Union[
40
+ typing_extensions.Literal["audio", "image", "video"], None
41
+ ] = None
42
+ mime, _ = mimetypes.guess_type(file_path)
43
+ if mime:
44
+ if mime.startswith("video/"):
45
+ file_type = "video"
46
+ elif mime.startswith("audio/"):
47
+ file_type = "audio"
48
+ elif mime.startswith("image/"):
49
+ file_type = "image"
50
+
51
+ if not file_type:
52
+ raise ValueError(
53
+ f"Could not determine file type for {file_path}. "
54
+ "Supported types: video (mp4, m4v, mov, webm), "
55
+ "audio (mp3, mpeg, wav, aac, aiff, flac), "
56
+ "image (png, jpg, jpeg, webp, avif, jp2, tiff, bmp)"
57
+ )
58
+
59
+ return file_type, ext
60
+
61
+
62
+ def _process_file_input(
63
+ file: typing.Union[str, pathlib.Path, typing.BinaryIO, io.IOBase],
64
+ ):
65
+ """
66
+ Process different file input types and return standardized information.
67
+
68
+ Args:
69
+ file: Path to the local file to upload, or a file-like object
70
+
71
+ Returns:
72
+ Tuple of (file_path, file_to_upload, file_type, extension)
73
+
74
+ Raises:
75
+ FileNotFoundError: If the local file is not found
76
+ ValueError: If the file type is not supported or file-like object is invalid
77
+ """
78
+
79
+ if isinstance(file, pathlib.Path):
80
+ file_path = str(file)
81
+ file_to_upload = None
82
+ elif isinstance(file, (io.IOBase, typing.BinaryIO)):
83
+ file_path = None
84
+ file_to_upload = file
85
+ else:
86
+ file_path = file
87
+ file_to_upload = None
88
+
89
+ if file_path is not None:
90
+ if not os.path.isfile(file_path):
91
+ raise FileNotFoundError(f"File not found: {file_path}")
92
+ file_type, extension = _get_file_type_and_extension(file_path)
93
+ else:
94
+ if file_to_upload is None:
95
+ raise ValueError("file_to_upload is None for file-like object case.")
96
+ file_name = getattr(file_to_upload, "name", None)
97
+ if not isinstance(file_name, str):
98
+ raise ValueError(
99
+ "File-like object must have a 'name' attribute of type str for extension detection."
100
+ )
101
+ file_type, extension = _get_file_type_and_extension(file_name)
102
+
103
+ return file_path, file_to_upload, file_type, extension
104
+
105
+
106
+ def _prepare_file_for_upload(
107
+ file_path: typing.Union[str, None],
108
+ file_to_upload: typing.Union[typing.BinaryIO, io.IOBase, None],
109
+ ) -> bytes:
110
+ """
111
+ Read file content for upload, handling both file paths and file-like objects.
112
+
113
+ Args:
114
+ file_path: Path to the file (if using file path)
115
+ file_to_upload: File-like object (if using file-like object)
116
+
117
+ Returns:
118
+ File content as bytes
119
+
120
+ Raises:
121
+ ValueError: If both or neither parameters are provided
122
+ """
123
+ if file_path is not None:
124
+ with open(file_path, "rb") as f:
125
+ return f.read()
126
+ else:
127
+ if file_to_upload is None:
128
+ raise ValueError("file_to_upload is None for file-like object case.")
129
+ pos = file_to_upload.tell() if hasattr(file_to_upload, "tell") else None
130
+ if hasattr(file_to_upload, "seek"):
131
+ file_to_upload.seek(0)
132
+ content = file_to_upload.read()
133
+ if pos is not None and hasattr(file_to_upload, "seek"):
134
+ file_to_upload.seek(pos)
135
+ return content
6
136
 
7
137
 
8
138
  class FilesClient:
139
+ """
140
+ Client for uploading files to Magic Hour's storage.
141
+
142
+ The Files client provides functionality to upload media files (images, videos, audio)
143
+ to Magic Hour's secure storage. Once uploaded, files can be referenced in other API
144
+ calls using the returned file path.
145
+
146
+ Supported file types:
147
+ - **Images**: PNG, JPG, JPEG, WebP, AVIF, JP2, TIFF, BMP
148
+ - **Videos**: MP4, M4V, MOV, WebM
149
+ - **Audio**: MP3, MPEG, WAV, AAC, AIFF, FLAC
150
+ """
151
+
9
152
  def __init__(self, *, base_client: SyncBaseClient):
10
153
  self._base_client = base_client
11
154
  self.upload_urls = UploadUrlsClient(base_client=self._base_client)
12
155
 
156
+ def upload_file(
157
+ self,
158
+ file: typing.Union[str, pathlib.Path, typing.BinaryIO, io.IOBase],
159
+ ) -> str:
160
+ """
161
+ Upload a file to Magic Hour's storage.
162
+
163
+ This method uploads a file to Magic Hour's secure cloud storage and returns
164
+ a file path that can be used as input for other Magic Hour API endpoints.
165
+ The file type is automatically detected from the file extension or MIME type.
166
+
167
+ Args:
168
+ file: The file to upload. Can be:
169
+ - **str**: Path to a local file (e.g., "/path/to/image.jpg")
170
+ - **str**: URL of the file to upload, this will be skipped and the URL will be returned as is
171
+ - **str**: if the string begins with "api-assets", the file will be assumed to be a blob path and already uploaded to Magic Hour's storage
172
+ - **pathlib.Path**: Path object to a local file
173
+ - **typing.BinaryIO or io.IOBase**: File-like object (must have a 'name' attribute)
174
+
175
+ Returns:
176
+ str: The uploaded file's path in Magic Hour's storage system.
177
+ This path can be used as input for other API endpoints, such for `.assets.image_file_path`.
178
+
179
+ Raises:
180
+ FileNotFoundError: If the specified local file doesn't exist.
181
+ ValueError: If the file type is not supported or file-like object is invalid.
182
+ httpx.HTTPStatusError: If the upload request fails (network/server errors).
183
+
184
+ Examples:
185
+ Upload a local image file:
186
+
187
+ ```python
188
+ from magic_hour import Client
189
+ from os import getenv
190
+
191
+ client = Client(token=getenv("MAGIC_HOUR_API_TOKEN"))
192
+
193
+ # Upload from file path
194
+ file_path = client.v1.files.upload_file("/path/to/your/image.jpg")
195
+ print(f"Uploaded file: {file_path}")
196
+
197
+ # Use the uploaded file in other API calls
198
+ result = client.v1.ai_image_upscaler.create(
199
+ assets={"image_file_path": file_path}, style={"upscale_factor": 2}
200
+ )
201
+ ```
202
+
203
+ Upload using pathlib.Path:
204
+
205
+ ```python
206
+ from pathlib import Path
207
+
208
+ image_path = Path("./assets/photo.png")
209
+ file_path = client.v1.files.upload_file(image_path)
210
+ ```
211
+
212
+ Upload from a file-like object:
213
+
214
+ ```python
215
+ with open("video.mp4", "rb") as video_file:
216
+ file_path = client.v1.files.upload_file(video_file)
217
+ ```
218
+ """
219
+
220
+ if isinstance(file, str) and file.startswith(("http://", "https://")):
221
+ logger.info(f"{file} is a url. Skipping upload and returning the URL.")
222
+ return file
223
+ elif isinstance(file, str) and file.startswith("api-assets"):
224
+ logger.info(
225
+ f"{file} is begins with api-assets, assuming it's a blob path.. Skipping upload and returning the path."
226
+ )
227
+ return file
228
+
229
+ file_path, file_to_upload, file_type, extension = _process_file_input(file)
230
+
231
+ response = self.upload_urls.create(
232
+ items=[
233
+ V1FilesUploadUrlsCreateBodyItemsItem(
234
+ extension=extension, type_=file_type
235
+ )
236
+ ]
237
+ )
238
+
239
+ if not response.items:
240
+ raise ValueError("No upload URL was returned from the server")
241
+
242
+ upload_info = response.items[0]
243
+
244
+ with httpx.Client(timeout=None) as client:
245
+ content = _prepare_file_for_upload(
246
+ file_path=file_path, file_to_upload=file_to_upload
247
+ )
248
+
249
+ upload_response = client.put(url=upload_info.upload_url, content=content)
250
+ upload_response.raise_for_status()
251
+
252
+ logger.info(
253
+ f"Uploaded {file_path} to Magic Hour storage at {upload_info.file_path}."
254
+ )
255
+ return upload_info.file_path
256
+
13
257
 
14
258
  class AsyncFilesClient:
259
+ """
260
+ Async client for uploading files to Magic Hour's storage.
261
+
262
+ The AsyncFilesClient provides asynchronous functionality to upload media files
263
+ (images, videos, audio) to Magic Hour's secure storage. This is ideal for
264
+ applications that need to handle multiple file uploads concurrently or integrate
265
+ with async frameworks like FastAPI or aiohttp.
266
+
267
+ Supported file types:
268
+ - **Images**: PNG, JPG, JPEG, WebP, AVIF, JP2, TIFF, BMP
269
+ - **Videos**: MP4, M4V, MOV, WebM
270
+ - **Audio**: MP3, MPEG, WAV, AAC, AIFF, FLAC
271
+ """
272
+
15
273
  def __init__(self, *, base_client: AsyncBaseClient):
16
274
  self._base_client = base_client
17
275
  self.upload_urls = AsyncUploadUrlsClient(base_client=self._base_client)
276
+
277
+ async def upload_file(
278
+ self,
279
+ file: typing.Union[str, pathlib.Path, typing.BinaryIO, io.IOBase],
280
+ ) -> str:
281
+ """
282
+ Upload a file to Magic Hour's storage asynchronously.
283
+
284
+ This method asynchronously uploads a file to Magic Hour's secure cloud storage
285
+ and returns a file path that can be used as input for other Magic Hour API endpoints.
286
+ The file type is automatically detected from the file extension or MIME type.
287
+
288
+ Args:
289
+ file: The file to upload. Can be:
290
+ - **str**: Path to a local file (e.g., "/path/to/image.jpg")
291
+ - **str**: URL of the file to upload, this will be skipped and the URL will be returned as is
292
+ - **str**: if the string begins with "api-assets", the file will be assumed to be a blob path and already uploaded to Magic Hour's storage
293
+ - **pathlib.Path**: Path object to a local file
294
+ - **typing.BinaryIO or io.IOBase**: File-like object (must have a 'name' attribute)
295
+
296
+ Returns:
297
+ str: The uploaded file's path in Magic Hour's storage system.
298
+ This path can be used as input for other API endpoints, such for `.assets.image_file_path`.
299
+
300
+ Raises:
301
+ FileNotFoundError: If the specified local file doesn't exist.
302
+ ValueError: If the file type is not supported or file-like object is invalid.
303
+ httpx.HTTPStatusError: If the upload request fails (network/server errors).
304
+
305
+ Examples:
306
+ Basic async upload:
307
+
308
+ ```python
309
+ import asyncio
310
+ from magic_hour import AsyncClient
311
+ from os import getenv
312
+
313
+
314
+ async def upload_example():
315
+ client = AsyncClient(token=getenv("MAGIC_HOUR_API_TOKEN"))
316
+
317
+ # Upload from file path
318
+ file_path = await client.v1.files.upload_file("/path/to/your/image.jpg")
319
+ print(f"Uploaded file: {file_path}")
320
+
321
+ # Use the uploaded file in other API calls
322
+ result = await client.v1.ai_image_upscaler.create(
323
+ assets={"image_file_path": file_path}, style={"upscale_factor": 2}
324
+ )
325
+
326
+
327
+ asyncio.run(upload_example())
328
+ ```
329
+ """
330
+ if isinstance(file, str) and file.startswith(("http://", "https://")):
331
+ logger.info(f"{file} is a url. Skipping upload and returning the URL.")
332
+ return file
333
+ elif isinstance(file, str) and file.startswith("api-assets"):
334
+ logger.info(
335
+ f"{file} is begins with api-assets, assuming it's a blob path.. Skipping upload and returning the path."
336
+ )
337
+ return file
338
+
339
+ file_path, file_to_upload, file_type, extension = _process_file_input(file)
340
+
341
+ response = await self.upload_urls.create(
342
+ items=[
343
+ V1FilesUploadUrlsCreateBodyItemsItem(
344
+ extension=extension, type_=file_type
345
+ )
346
+ ]
347
+ )
348
+
349
+ if not response.items:
350
+ raise ValueError("No upload URL was returned from the server")
351
+
352
+ upload_info = response.items[0]
353
+
354
+ async with httpx.AsyncClient(timeout=None) as client:
355
+ content = _prepare_file_for_upload(
356
+ file_path=file_path, file_to_upload=file_to_upload
357
+ )
358
+
359
+ upload_response = await client.put(
360
+ url=upload_info.upload_url, content=content
361
+ )
362
+ upload_response.raise_for_status()
363
+
364
+ logger.info(
365
+ f"Uploaded {file_path} to Magic Hour storage at {upload_info.file_path}."
366
+ )
367
+ return upload_info.file_path